Merge branch 'master' into health_step_goal_notification

pull/2442/head
Marco H 2023-01-06 09:50:27 +01:00
commit f160b7edbe
39 changed files with 852 additions and 803 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -3,18 +3,16 @@ A very minimalistic clock.
![](screenshot.png)
## 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,12 +2,10 @@
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 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:
![Screenshot](screenshot2.png)
![Screenshot](legend.png)
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.
![Lost](lost.png)
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

View File

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

File diff suppressed because one or more lines are too long

View File

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

BIN
apps/gipy/legend.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
apps/gipy/lost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

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

View File

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

View File

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

View File

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

BIN
apps/gipy/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

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

View File

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

138
apps/grocery/interface.html Normal file
View File

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

View File

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