mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into health_step_goal_notification
commit
f160b7edbe
|
@ -1,2 +1,3 @@
|
|||
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)
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
* Works with Bangle 2
|
||||
* 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
|
||||
* Other time zones
|
||||
* Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
|
||||
|
|
|
@ -18,19 +18,29 @@ var timervalue = 0;
|
|||
var istimeron = false;
|
||||
var timertick;
|
||||
|
||||
Bangle.on('touch',t=>{
|
||||
if (t == 1) {
|
||||
Bangle.on('touch',(touchside, touchdata)=>{
|
||||
if (touchside == 1) {
|
||||
Bangle.buzz(30);
|
||||
if (timervalue < 5*60) { timervalue = 1 ; }
|
||||
else { timervalue -= 5*60; }
|
||||
var changevalue = 0;
|
||||
if(touchdata.y > 88) {
|
||||
changevalue += 60*5;
|
||||
} else {
|
||||
changevalue += 60*1;
|
||||
}
|
||||
if (timervalue < changevalue) { timervalue = 1 ; }
|
||||
else { timervalue -= changevalue; }
|
||||
}
|
||||
else if (t == 2) {
|
||||
else if (touchside == 2) {
|
||||
Bangle.buzz(30);
|
||||
if (!istimeron) {
|
||||
istimeron = true;
|
||||
timertick = setInterval(countDown, 1000);
|
||||
}
|
||||
timervalue += 60*10;
|
||||
if(touchdata.y > 88) {
|
||||
timervalue += 60*10;
|
||||
} else {
|
||||
timervalue += 60*1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -73,7 +83,7 @@ function countDown() {
|
|||
function showWelcomeMessage() {
|
||||
g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6);
|
||||
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);
|
||||
setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000);
|
||||
}
|
||||
|
@ -103,18 +113,21 @@ function draw() {
|
|||
g.reset().clearRect(0,24,g.getWidth(),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.reset();
|
||||
|
||||
var x_night_start = 176 - (((getGmt().getHours()-6)%24) / 24 * 176 + 4);
|
||||
var x_night_end = 176 - (((getGmt().getHours()+6)%24) / 24 * 176 + 4);
|
||||
for (let x = x_night_start; x < 176; x+=2) {
|
||||
g.setColor('#000').drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||
var x_night_start = 176 - (((gmtHours-6)%24) / 24 * 176 + 4);
|
||||
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.setColor('#000').drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||
g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "a_clock_timer",
|
||||
"name": "A Clock with Timer",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A Clock with Timer, Map and Time Zones",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
0.03: Indicate battery level through line occurrence.
|
||||
0.04: Use widget_utils module.
|
||||
0.05: Support for clkinfo.
|
||||
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
|
||||
0.07: Use clock_info.addInteractive instead of a custom implementation
|
|
@ -11,8 +11,7 @@ The original output of stable diffusion is shown here:
|
|||
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||
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
|
||||
implementes the clkinfo module and can be configured via touch left/right/up/down.
|
||||
Touch at the center to trigger the selected action.
|
||||
implements the clkinfo module and can be configured via touch and swipe left/right and up/down.
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/************************************************
|
||||
* AI Clock
|
||||
*/
|
||||
const storage = require('Storage');
|
||||
const clock_info = require("clock_info");
|
||||
|
||||
|
||||
|
@ -21,124 +20,14 @@ Graphics.prototype.setFontGochiHand = function(scale) {
|
|||
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;
|
||||
|
||||
|
||||
/************************************************
|
||||
* 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;
|
||||
}
|
||||
|
||||
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() {
|
||||
function drawBackground(start, end) {
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setColor("#000");
|
||||
|
||||
var bat = E.getBattery() / 100.0;
|
||||
var y = 0;
|
||||
while(y < H){
|
||||
var y = start;
|
||||
while(y < end){
|
||||
// Show less lines in case of small battery level.
|
||||
if(Math.random() > bat){
|
||||
y += 5;
|
||||
|
@ -154,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){
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(cx, cy, 12);
|
||||
|
@ -163,54 +76,6 @@ function drawCircle(isLocked){
|
|||
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
|
||||
}
|
||||
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(){
|
||||
// Draw digital time first
|
||||
|
@ -267,34 +132,23 @@ function drawDigits(){
|
|||
}
|
||||
|
||||
|
||||
function drawMenu(){
|
||||
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
|
||||
var item = menuEntry.items[settings.menuPosY-1].get();
|
||||
drawMenuItem(item.text, item.img);
|
||||
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 draw(){
|
||||
function drawMainClock(){
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
g.setColor(1,1,1);
|
||||
g.setColor("#fff");
|
||||
g.reset().clearRect(0, clkInfoY, g.getWidth(), g.getHeight());
|
||||
|
||||
drawBackground();
|
||||
drawMenu();
|
||||
drawBackground(clkInfoY, H);
|
||||
drawTime();
|
||||
drawCircle(Bangle.isLocked());
|
||||
}
|
||||
|
||||
|
@ -304,7 +158,7 @@ function draw(){
|
|||
*/
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(true);
|
||||
draw();
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
@ -315,62 +169,10 @@ Bangle.on('lock', function(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(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(){
|
||||
try{
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
} catch(ex){
|
||||
// If this fails, we still kill the app...
|
||||
}
|
||||
clockInfoMenu.remove();
|
||||
delete clockInfoMenu;
|
||||
});
|
||||
|
||||
|
||||
|
@ -386,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.
|
||||
*/
|
||||
|
@ -400,7 +251,7 @@ Bangle.loadWidgets();
|
|||
require('widget_utils').hide();
|
||||
|
||||
// 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();
|
||||
|
||||
// After drawing the watch face, we can draw the widgets
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "AI Clock",
|
||||
"shortName":"AI Clock",
|
||||
"icon": "aiclock.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||
|
|
|
@ -24,4 +24,4 @@
|
|||
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
|
||||
|
|
|
@ -3,18 +3,16 @@ A very minimalistic clock.
|
|||
|
||||

|
||||
|
||||
## ToDos and known issues
|
||||
- [ ] The clkinfo is always shown and its, therefore, not possible to only show the time as shown in the screenshot.
|
||||
- [ ] The weeknumber is currently not an option in clkinfo.
|
||||
- [ ] Its not possible to run clkinfo items (e.g. trigger home assistant).
|
||||
|
||||
## Features
|
||||
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
|
||||
and additionally allows you to send triggers directly from the clock (select triggers via up/down and
|
||||
send via click center). Here are examples of other apps that are integrated:
|
||||
|
||||
- Bangle data such as steps, heart rate, battery or charging state.
|
||||
- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled*
|
||||
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
|
||||
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
|
||||
|
||||
Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
|
||||
For example, if you install the HomeAssistant app, this menu item will be shown if you first
|
||||
touch the bottom of the screen and then swipe left/right to the home assistant menu. To select
|
||||
sub-items simply swipe up/down.
|
||||
|
||||
## Settings
|
||||
- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
|
||||
|
@ -22,25 +20,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.
|
||||
- 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 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>
|
||||
|
|
|
@ -31,6 +31,19 @@ for (const key in saved_settings) {
|
|||
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);
|
||||
}
|
||||
|
||||
/************************************************
|
||||
* Assets
|
||||
*/
|
||||
|
@ -84,72 +97,48 @@ let imgLock = function() {
|
|||
|
||||
|
||||
/************************************************
|
||||
* Menu
|
||||
* Clock Info
|
||||
*/
|
||||
// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
|
||||
let bwItems = {
|
||||
name: null,
|
||||
img: null,
|
||||
items: [
|
||||
{ name: "WeekOfYear",
|
||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
||||
show: function() {},
|
||||
hide: function () {}
|
||||
},
|
||||
]
|
||||
};
|
||||
let clockInfoItems = clock_info.load();
|
||||
let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
|
||||
x : 0,
|
||||
y: 135,
|
||||
w: W,
|
||||
h: H-135,
|
||||
draw : (itm, info, options) => {
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
|
||||
|
||||
let weekOfYear = function() {
|
||||
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);
|
||||
};
|
||||
g.setFontAlign(0,0);
|
||||
g.setColor(g.theme.bg);
|
||||
|
||||
|
||||
// Load menu
|
||||
let menu = clock_info.load();
|
||||
menu = menu.concat(bwItems);
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
let canRunMenuItem = function() {
|
||||
if(settings.menuPosY == 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
return item.run !== undefined;
|
||||
};
|
||||
|
||||
|
||||
let runMenuItem = function() {
|
||||
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);
|
||||
if (options.focus){
|
||||
g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); // show if focused
|
||||
g.drawRect(options.x+1, options.y+1, options.x+options.w-3, options.y+options.h-2); // show if focused
|
||||
}
|
||||
} catch (ex) {
|
||||
// Simply ignore it...
|
||||
|
||||
// Set text and font
|
||||
var image = info.img;
|
||||
var text = String(info.text);
|
||||
if(text.split('\n').length > 1){
|
||||
g.setMiniFont();
|
||||
} else {
|
||||
g.setSmallFont();
|
||||
}
|
||||
|
||||
// Compute sizes
|
||||
var strWidth = g.stringWidth(text);
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var midx = options.x+options.w/2;
|
||||
|
||||
// Draw
|
||||
if (image) {
|
||||
var scale = imgWidth / image.width;
|
||||
g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale});
|
||||
}
|
||||
g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/************************************************
|
||||
|
@ -161,7 +150,7 @@ let draw = function() {
|
|||
|
||||
// Draw clock
|
||||
drawDate();
|
||||
drawMenuAndTime();
|
||||
drawTime();
|
||||
drawLock();
|
||||
drawWidgets();
|
||||
};
|
||||
|
@ -169,7 +158,7 @@ let draw = function() {
|
|||
|
||||
let drawDate = function() {
|
||||
// Draw background
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
var y = getLineY()
|
||||
g.reset().clearRect(0,0,W,y);
|
||||
|
||||
// Draw date
|
||||
|
@ -197,14 +186,12 @@ let drawDate = function() {
|
|||
};
|
||||
|
||||
|
||||
let drawTime = function(y, smallText) {
|
||||
let drawTime = function() {
|
||||
// Draw background
|
||||
var y1 = getLineY();
|
||||
var y = y1;
|
||||
var date = new Date();
|
||||
|
||||
// Draw time
|
||||
g.setColor(g.theme.bg);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
var hours = String(date.getHours());
|
||||
var minutes = date.getMinutes();
|
||||
minutes = minutes < 10 ? String("0") + minutes : minutes;
|
||||
|
@ -212,67 +199,18 @@ let drawTime = function(y, smallText) {
|
|||
var timeStr = hours + colon + minutes;
|
||||
|
||||
// Set y coordinates correctly
|
||||
y += parseInt((H - y)/2) + 5;
|
||||
y += parseInt((H - y)/2)-10;
|
||||
|
||||
// Show large or small time depending on info entry
|
||||
if(smallText){
|
||||
y -= 15;
|
||||
g.setMediumFont();
|
||||
} else {
|
||||
g.setLargeFont();
|
||||
}
|
||||
// Clear region
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(0,y1,W,y+20);
|
||||
|
||||
g.setMediumFont();
|
||||
g.setColor(g.theme.bg);
|
||||
g.setFontAlign(0,0);
|
||||
g.drawString(timeStr, W/2, y);
|
||||
};
|
||||
|
||||
let drawMenuItem = function(text, image) {
|
||||
// First clear the time region
|
||||
var y = H/5*2 + (isFullscreen() ? 0 : 8);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillRect(0,y,W,H);
|
||||
|
||||
// Draw menu text
|
||||
var hasText = (text != null && text != "");
|
||||
if(hasText){
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
// For multiline text we show an even smaller font...
|
||||
text = String(text);
|
||||
if(text.split('\n').length > 1){
|
||||
g.setMiniFont();
|
||||
} else {
|
||||
g.setSmallFont();
|
||||
}
|
||||
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var strWidth = g.stringWidth(text);
|
||||
g.setColor(g.theme.fg).fillRect(0, 149-14, W, H);
|
||||
g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3);
|
||||
|
||||
if(image != null){
|
||||
var scale = imgWidth / image.width;
|
||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale});
|
||||
}
|
||||
}
|
||||
|
||||
// Draw time
|
||||
drawTime(y, hasText);
|
||||
};
|
||||
|
||||
|
||||
let drawMenuAndTime = 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
|
||||
var item = menuEntry.items[settings.menuPosY-1].get();
|
||||
drawMenuItem(item.text, item.img);
|
||||
};
|
||||
|
||||
let drawLock = function() {
|
||||
if(settings.showLock && Bangle.isLocked()){
|
||||
|
@ -291,17 +229,6 @@ let drawWidgets = function() {
|
|||
};
|
||||
|
||||
|
||||
let isFullscreen = function() {
|
||||
var s = settings.screen.toLowerCase();
|
||||
if(s == "dynamic"){
|
||||
return Bangle.isLocked();
|
||||
} else {
|
||||
return s == "full";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/************************************************
|
||||
* Listener
|
||||
*/
|
||||
|
@ -343,74 +270,12 @@ let lockListenerBw = function(isLocked) {
|
|||
};
|
||||
Bangle.on('lock', lockListenerBw);
|
||||
|
||||
let chargingListenerBw = function(charging) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
// Jump to battery
|
||||
settings.menuPosX = 0;
|
||||
settings.menuPosY = 1;
|
||||
draw();
|
||||
let kill = function(){
|
||||
clockInfoMenu.remove();
|
||||
delete clockInfoMenu;
|
||||
};
|
||||
Bangle.on('charging', chargingListenerBw);
|
||||
|
||||
let touchListenerBw = 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(is_lower){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
|
||||
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_upper){
|
||||
if(e.y < widget_size){
|
||||
return;
|
||||
}
|
||||
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = settings.menuPosY-1;
|
||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
|
||||
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_right){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
||||
settings.menuPosY = 0;
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_left){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = 0;
|
||||
settings.menuPosX = settings.menuPosX-1;
|
||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
||||
drawMenuAndTime();
|
||||
}
|
||||
|
||||
if(is_center){
|
||||
if(canRunMenuItem()){
|
||||
runMenuItem();
|
||||
}
|
||||
}
|
||||
};
|
||||
Bangle.on('touch', touchListenerBw);
|
||||
|
||||
let save = () => storage.write(SETTINGS_FILE, settings);
|
||||
E.on("kill", save);
|
||||
E.on("kill", kill);
|
||||
|
||||
/************************************************
|
||||
* Startup Clock
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.25",
|
||||
"description": "A very minimalistic clock to mainly show date and time.",
|
||||
"version": "0.26",
|
||||
"description": "A very minimalistic clock.",
|
||||
"readme": "README.md",
|
||||
"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",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB |
|
@ -23,4 +23,6 @@ button to exit is no longer an option.
|
|||
facilitate 'fast switching' of apps where available.
|
||||
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.
|
||||
0.21: Bangle 2: Call Bangle.drawWidgets() early on so that the widget field
|
||||
immediately follows the correct theme.
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
g.flip();
|
||||
};
|
||||
|
||||
Bangle.drawWidgets(); // To immediately update widget field to follow current theme - remove leftovers if previous app set custom theme.
|
||||
Bangle.loadWidgets();
|
||||
drawPage(0);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.20",
|
||||
"version": "0.21",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.1: New App!
|
||||
0.1: New App!
|
||||
0.2: Now with timer function
|
||||
|
|
|
@ -6,4 +6,16 @@ Things I changed:
|
|||
|
||||
- The main font for the time is now Audiowide
|
||||
- Removed the written out day name and replaced it with steps and bpm
|
||||
- Changed the date string to a (for me) more readable string
|
||||
- Changed the date string to a (for me) more readable string
|
||||
|
||||
Timer function:
|
||||
- Touch the right side, to start the timer
|
||||
- Initial timer timeout is 300s/5min
|
||||
- Right touch again, add 300s/5min to timeout
|
||||
- Left touch, decrease timeout by 60s/1min
|
||||
- So it is easy, to add timeouts like 7min/3min or 12min
|
||||
- Special thanks to the maintainer of the a_clock_timer app from which I borrowed the code.
|
||||
|
||||
Todo:
|
||||
- Make displayed information configurable, after https://github.com/espruino/BangleApps/issues/2226
|
||||
- Clean up code
|
||||
|
|
|
@ -1,67 +1,127 @@
|
|||
// Fonts
|
||||
Graphics.prototype.setFontAudiowide = function() {
|
||||
// Actual height 33 (36 - 4)
|
||||
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 scale = 1; // size multiplier for this font
|
||||
g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
// Globals variables
|
||||
var timervalue = 0;
|
||||
var istimeron = false;
|
||||
var timertick;
|
||||
|
||||
// Functions
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
steps = WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
} else {
|
||||
steps = Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
var steps = 0;
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
steps = WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
} else {
|
||||
steps = Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
} catch(ex) {
|
||||
// In case we failed, we can only show 0 steps.
|
||||
return "?";
|
||||
// In case we failed, we can only show 0 steps.
|
||||
return "?";
|
||||
}
|
||||
|
||||
return Math.round(steps);
|
||||
return Math.round(steps);
|
||||
}
|
||||
|
||||
function timeToString(duration) {
|
||||
var hrs = ~~(duration / 3600);
|
||||
var mins = ~~((duration % 3600) / 60);
|
||||
var secs = ~~duration % 60;
|
||||
var ret = "";
|
||||
if (hrs > 0) {
|
||||
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
|
||||
}
|
||||
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
|
||||
ret += "" + secs;
|
||||
return ret;
|
||||
}
|
||||
|
||||
function countDown() {
|
||||
timervalue--;
|
||||
|
||||
g.reset().clearRect(0, 40, 44+99, g.getHeight()/2-25);
|
||||
|
||||
g.setFontAlign(0, -1, 0);
|
||||
g.setFont("6x8", 2).drawString(timeToString(timervalue), 95, g.getHeight()/2-50);
|
||||
|
||||
if (timervalue <= 0) {
|
||||
istimeron = false;
|
||||
clearInterval(timertick);
|
||||
|
||||
Bangle.buzz().then(()=>{
|
||||
return new Promise(resolve=>setTimeout(resolve, 500));
|
||||
}).then(()=>{
|
||||
return Bangle.buzz(1000);
|
||||
});
|
||||
}
|
||||
else
|
||||
if ((timervalue <= 30) && (timervalue % 10 == 0)) { Bangle.buzz(); }
|
||||
}
|
||||
|
||||
// Touch
|
||||
Bangle.on('touch',t => {
|
||||
if (t == 1) {
|
||||
// Touch on the left, reduce timervalue about 60s
|
||||
Bangle.buzz(30);
|
||||
if (timervalue < 60) { timervalue = 1 ; }
|
||||
else { timervalue -= 60; }
|
||||
}
|
||||
// Touch on the right, raise timervaule about 300s
|
||||
else if (t == 2) {
|
||||
Bangle.buzz(30);
|
||||
if (!istimeron) {
|
||||
istimeron = true;
|
||||
timertick = setInterval(countDown, 1000);
|
||||
}
|
||||
timervalue += 60*5;
|
||||
}
|
||||
});
|
||||
|
||||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||
let drawTimeout;
|
||||
let drawTimeout;
|
||||
|
||||
|
||||
// Actually draw the watch face
|
||||
let draw = function() {
|
||||
var x = g.getWidth() / 2;
|
||||
var y = g.getHeight() / 2;
|
||||
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date, 1); // Hour and minute
|
||||
g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
|
||||
var dateStr = require("locale").date(date, 1).toUpperCase();
|
||||
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
|
||||
g.setFontAlign(0, 0).setFont("6x8", 2);
|
||||
g.drawString(getSteps(), 50, y+70);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
||||
// Actually draw the watch face
|
||||
let draw = function() {
|
||||
var x = g.getWidth() / 2;
|
||||
var y = g.getHeight() / 2;
|
||||
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date, 1); // Hour and minute
|
||||
g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y);
|
||||
var dateStr = require("locale").date(date, 1).toUpperCase();
|
||||
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28);
|
||||
g.setFontAlign(0, 0).setFont("6x8", 2);
|
||||
g.drawString(getSteps(), 50, y+70);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70);
|
||||
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
};
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Called to unload all of the clock app
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
delete Graphics.prototype.setFontAnton;
|
||||
}});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
draw();
|
||||
setTimeout(Bangle.drawWidgets,0);
|
||||
}
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
};
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Called to unload all of the clock app
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
delete Graphics.prototype.setFontAnton;
|
||||
}});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
draw();
|
||||
setTimeout(Bangle.drawWidgets,0);
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "entonclk",
|
||||
"name": "Enton Clock",
|
||||
"version": "0.1",
|
||||
"description": "A simple clock using the Audiowide font. ",
|
||||
"version": "0.2",
|
||||
"description": "A simple clock using the Audiowide font with timer. ",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
|
|
|
@ -63,3 +63,13 @@
|
|||
* Record traveled distance to get a good average speed.
|
||||
* Breaks (low speed) will not count in average speed.
|
||||
* Bugfix in average speed.
|
||||
|
||||
0.16:
|
||||
* When lost indicates nearest point on path.
|
||||
* Rescale display if lost and too far.
|
||||
* New setting to hide points and increase display speed.
|
||||
* Speed optimisations.
|
||||
* Estimated time of Arrival/Going back.
|
||||
* Display current and next segment in red so that you know where to go.
|
||||
* Avoid angles flickering at low speed at the cost of less refresh.
|
||||
* Splash screen while waiting for gps signal.
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
Gipy allows you to follow gpx traces on your watch.
|
||||
|
||||

|
||||

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

|
||||

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

|
||||
|
||||
The distance to next point displayed corresponds to the length of the black segment.
|
||||
|
||||
### Settings
|
||||
|
||||
|
@ -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
|
||||
- max speed : used to compute how long to turn the gps off
|
||||
- display points : display/hide points (not waypoints)
|
||||
|
||||
### Caveats
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
|
||||
* bugs
|
||||
|
||||
- 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
|
||||
+ use Bangle.project(latlong)
|
||||
|
||||
* additional features
|
||||
|
||||
|
@ -15,7 +10,6 @@
|
|||
(and look at more than next point)
|
||||
|
||||
- display distance to next water/toilet ?
|
||||
- dynamic map rescale
|
||||
- display scale (100m)
|
||||
|
||||
- compress path ?
|
||||
|
|
442
apps/gipy/app.js
442
apps/gipy/app.js
File diff suppressed because one or more lines are too long
|
@ -182,12 +182,21 @@ document
|
|||
document
|
||||
.getElementById("upload")
|
||||
.addEventListener('click', function() {
|
||||
document.getElementById('upload').disabled = true;
|
||||
status.innerHTML = "uploading file";
|
||||
console.log("uploading");
|
||||
let gpc_string = vec_to_string(gpc_content);
|
||||
Util.writeStorage(gpc_filename + ".gpc", gpc_string, () => {
|
||||
status.innerHTML = `${gpc_filename}.gpc uploaded`;
|
||||
console.log("DONE");
|
||||
status.innerHTML = "Checking upload";
|
||||
Util.readStorage(gpc_filename + ".gpc", uploaded_content => {
|
||||
if (uploaded_content == gpc_string) {
|
||||
status.innerHTML = `${gpc_filename}.gpc uploaded`;
|
||||
console.log("DONE");
|
||||
} else {
|
||||
status.innerHTML = "Upload FAILED";
|
||||
document.getElementById('upload').disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
|
@ -2,13 +2,13 @@
|
|||
"id": "gipy",
|
||||
"name": "Gipy",
|
||||
"shortName": "Gipy",
|
||||
"version": "0.15",
|
||||
"description": "Follow gpx files",
|
||||
"version": "0.16",
|
||||
"description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.",
|
||||
"allow_emulator":false,
|
||||
"icon": "gipy.png",
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"screenshots": [],
|
||||
"screenshots": [{"url":"splash.png"}],
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
|
|
|
@ -46,11 +46,11 @@ export interface InitOutput {
|
|||
readonly __wbindgen_malloc: (a: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240: (a: number, b: number, c: number, d: number) => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
|
|
@ -98,14 +98,6 @@ function getInt32Memory0() {
|
|||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
|
||||
cachedTextDecoder.decode();
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
|
@ -115,6 +107,14 @@ function addHeapObject(obj) {
|
|||
return idx;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
|
||||
cachedTextDecoder.decode();
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function debugString(val) {
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
|
@ -205,7 +205,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_24(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function _assertClass(instance, klass) {
|
||||
|
@ -310,7 +310,7 @@ function handleError(f, args) {
|
|||
}
|
||||
}
|
||||
function __wbg_adapter_69(arg0, arg1, arg2, arg3) {
|
||||
wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
wasm.wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,13 +371,13 @@ async function load(module, imports) {
|
|||
function getImports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_gpcsvg_new = function(arg0) {
|
||||
const ret = GpcSvg.__wrap(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
|
@ -386,15 +386,15 @@ function getImports() {
|
|||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
const ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) {
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_fetch_3894579f6e2af3be = function(arg0) {
|
||||
const ret = fetch(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
@ -558,10 +558,6 @@ function getImports() {
|
|||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
|
||||
const ret = JSON.stringify(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.get(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
|
@ -574,6 +570,10 @@ function getImports() {
|
|||
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
|
||||
const ret = JSON.stringify(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||
const ret = debugString(getObject(arg1));
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
|
@ -588,8 +588,8 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24);
|
||||
imports.wbg.__wbindgen_closure_wrapper929 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 143, __wbg_adapter_24);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
|
@ -9,8 +9,8 @@ export function convert_gpx_strings(a: number, b: number, c: number, d: number,
|
|||
export function __wbindgen_malloc(a: number): number;
|
||||
export function __wbindgen_realloc(a: number, b: number, c: number): number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
export function __wbindgen_free(a: number, b: number): void;
|
||||
export function __wbindgen_exn_store(a: number): void;
|
||||
export function wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(a: number, b: number, c: number, d: number): void;
|
||||
export function wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240(a: number, b: number, c: number, d: number): void;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Refactor code to store grocery list in separate file
|
||||
0.03: Sort selected items to bottom and enable Widgets
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var filename = 'grocery_list.json';
|
||||
var settings = require("Storage").readJSON(filename,1)|| { products: [] };
|
||||
let menu;
|
||||
|
||||
function updateSettings() {
|
||||
require("Storage").writeJSON(filename, settings);
|
||||
|
@ -11,19 +12,32 @@ function twoChat(n){
|
|||
return ''+n;
|
||||
}
|
||||
|
||||
const mainMenu = settings.products.reduce(function(m, p, i){
|
||||
const name = twoChat(p.quantity)+' '+p.name;
|
||||
m[name] = {
|
||||
value: p.ok,
|
||||
format: v => v?'[x]':'[ ]',
|
||||
onchange: v => {
|
||||
settings.products[i].ok = v;
|
||||
updateSettings();
|
||||
}
|
||||
};
|
||||
return m;
|
||||
}, {
|
||||
'': { 'title': 'Grocery list' }
|
||||
});
|
||||
function sortMenu() {
|
||||
mainMenu.sort((a,b) => {
|
||||
const byValue = a.value-b.value;
|
||||
return byValue !== 0 ? byValue : a.index-b.index;
|
||||
});
|
||||
if (menu) {
|
||||
menu.draw();
|
||||
}
|
||||
}
|
||||
|
||||
const mainMenu = settings.products.map((p,i) => ({
|
||||
title: twoChat(p.quantity)+' '+p.name,
|
||||
value: p.ok,
|
||||
format: v => v?'[x]':'[ ]',
|
||||
index: i,
|
||||
onchange: v => {
|
||||
settings.products[i].ok = v;
|
||||
updateSettings();
|
||||
sortMenu();
|
||||
}
|
||||
}));
|
||||
sortMenu();
|
||||
|
||||
mainMenu[''] = { 'title': 'Grocery list' };
|
||||
mainMenu['< Back'] = ()=>{load();};
|
||||
E.showMenu(mainMenu);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
menu = E.showMenu(mainMenu);
|
||||
Bangle.drawWidgets();
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h4>List of products</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>name</th>
|
||||
<th>quantity</th>
|
||||
<th>done</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="products">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<br><br>
|
||||
<h4>Add a new product</h4>
|
||||
<form id="add_product_form">
|
||||
<div class="columns">
|
||||
<div class="column col-4 col-xs-12">
|
||||
<input class="form-input input-sm" type="text" id="add_product_name" placeholder="Name">
|
||||
</div>
|
||||
<div class="column col-4 col-xs-12">
|
||||
<input class="form-input input-sm" value="1" type="number" id="add_product_quantity" placeholder="Quantity">
|
||||
</div>
|
||||
<div class="column col-4 col-xs-12">
|
||||
<button id="add_product_button" class="btn btn-primary btn-sm">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br><br>
|
||||
<button id="reset" class="btn btn-error">Reload</button>
|
||||
<button id="removeChecked" class="btn">Clear checked</button>
|
||||
<button id="save" class="btn btn-primary">Save</button>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
|
||||
<script>
|
||||
let settings;
|
||||
let products;
|
||||
|
||||
var $name = document.getElementById('add_product_name')
|
||||
var $form = document.getElementById('add_product_form')
|
||||
var $button = document.getElementById('add_product_button')
|
||||
var $quantity = document.getElementById('add_product_quantity')
|
||||
var $list = document.getElementById('products')
|
||||
var $reset = document.getElementById('reset')
|
||||
|
||||
$reset.addEventListener('click', reset)
|
||||
document.getElementById('save').addEventListener('click', save);
|
||||
document.getElementById('removeChecked').addEventListener('click', removeChecked);
|
||||
|
||||
$form.addEventListener('submit', event => {
|
||||
event.preventDefault()
|
||||
|
||||
var name = $name.value.trim()
|
||||
if(!name) return;
|
||||
|
||||
var quantity = parseInt($quantity.value)
|
||||
|
||||
products.push({
|
||||
name, quantity,
|
||||
ok: false
|
||||
})
|
||||
|
||||
renderProducts()
|
||||
$name.value = ''
|
||||
$quantity.value = 1
|
||||
})
|
||||
|
||||
function getData() {
|
||||
// show loading window
|
||||
Util.showModal("Loading...");
|
||||
Util.readStorage('grocery_list.json', data=>{
|
||||
// remove window
|
||||
Util.hideModal();
|
||||
|
||||
settings = JSON.parse(data || "{products: []}");
|
||||
products = settings.products;
|
||||
renderProducts();
|
||||
});
|
||||
}
|
||||
|
||||
function save(){
|
||||
settings.products = products;
|
||||
Util.showModal("Saving...");
|
||||
localStorage.setItem('grocery-product-list',JSON.stringify(products));
|
||||
Util.writeStorage("grocery_list.json", JSON.stringify(settings), () => {
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
|
||||
function reset(){
|
||||
getData();
|
||||
}
|
||||
|
||||
function removeProduct(index){
|
||||
products = products.filter((p,i) => i!==index)
|
||||
renderProducts()
|
||||
}
|
||||
|
||||
function handleChecked(index) {
|
||||
products[index].ok = !products[index].ok;
|
||||
}
|
||||
|
||||
function removeChecked() {
|
||||
products = products.filter(p => !p.ok);
|
||||
renderProducts()
|
||||
}
|
||||
|
||||
function renderProducts(){
|
||||
$list.innerHTML = ''
|
||||
products.forEach((product,index) => {
|
||||
var $product = document.createElement('tr')
|
||||
$product.innerHTML = `<td>${product.name}</td>
|
||||
<td>${product.quantity}</td>
|
||||
<td><input type="checkbox" ${product.ok ? "checked" : ""} onchange="handleChecked(${index})"></td>
|
||||
<td><button class="btn btn-error" onclick="removeProduct(${index})">remove</button></td>`
|
||||
$list.appendChild($product)
|
||||
})
|
||||
|
||||
$name.focus()
|
||||
|
||||
}
|
||||
|
||||
// Called when app starts
|
||||
function onInit() {
|
||||
getData();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"id": "grocery",
|
||||
"name": "Grocery",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.",
|
||||
"icon": "grocery.png",
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors,shopping,list",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"custom": "grocery.html",
|
||||
"interface": "interface.html",
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"grocery.app.js","url":"app.js"},
|
||||
|
|
Loading…
Reference in New Issue