Merge branch 'master' into health_step_goal_notification

pull/2442/head
Marco H 2023-01-05 11:37:18 +01:00
commit 16efd43ce9
101 changed files with 1509 additions and 444 deletions

View File

@ -4,4 +4,5 @@ apps/schoolCalendar/fullcalendar/main.js
apps/authentiwatch/qr_packed.js
apps/qrcode/qr-scanner.umd.min.js
apps/gipy/pkg/gpconv.js
apps/health/chart.min.js
*.test.js

View File

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

View File

@ -98,7 +98,7 @@ This is the best way to test...
**Note:** It's a great idea to get a local copy of the repository on your PC,
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
that there might be.
that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.

View File

@ -1 +1,2 @@
0.01: Beta version for Bangle 2 (2021/11/28)
0.02: Shows night time on the map (2022/12/28)

View File

@ -7,7 +7,7 @@
* Other time zones
* Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
* World Map
* The yellow line shows the position of the sun
* The map shows day and night on Earth and the position of the Sun (yellow line)
![](screenshot.png)

View File

@ -102,13 +102,24 @@ function queueNextDraw() {
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);
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());
}
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());
}
}
var locale = require("locale");
var date = new Date();
g.setFontAlign(0,0);
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);

View File

@ -1,7 +1,7 @@
{
"id": "a_clock_timer",
"name": "A Clock with Timer",
"version": "0.01",
"version": "0.02",
"description": "A Clock with Timer, Map and Time Zones",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,2 +1,3 @@
1.00: Release (2021/12/01)
1.01: Grey font when timer is frozen (2021/12/04)
1.02: Force light theme, since the app is not designed for dark theme (2022/12/28)

View File

@ -166,6 +166,7 @@ function draw() {
g.drawRect(88+8,138-24, 176-10, 138+22);
}
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
require("FontHaxorNarrow7x17").add(Graphics);
g.clear();
Bangle.loadWidgets();

View File

@ -2,7 +2,7 @@
"id":"a_speech_timer",
"name":"Speech Timer",
"icon": "app.png",
"version":"1.01",
"version":"1.02",
"description": "A timer designed to help keeping your speeches and presentations to time.",
"tags": "tool,timer",
"readme":"README.md",

View File

@ -9,3 +9,5 @@
Fix clkinfo icon
0.09: Ensure Agenda supplies an image for clkinfo items
0.10: Update clock_info to avoid a redraw
0.11: Setting to use "Today" and "Yesterday" instead of dates
Added dynamic, short and range fields to clkinfo

View File

@ -1,7 +1,14 @@
(function() {
function getPassedSec(date) {
var now = new Date();
var passed = (now-date)/1000;
if(passed<0) return 0;
return passed;
}
var agendaItems = {
name: "Agenda",
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
dynamic: true,
items: []
};
var locale = require("locale");
@ -15,11 +22,15 @@
var title = entry.title.slice(0,12);
var date = new Date(entry.timestamp*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
agendaItems.items.push({
name: "Agenda "+i,
get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }),
hasRange: true,
get: () => ({ text: title + "\n" + dateStr,
img: agendaItems.img, short: shortStr.trim(),
v: getPassedSec(date), min: 0, max: entry.durationInSeconds}),
show: function() {},
hide: function () {}
});

View File

@ -33,16 +33,32 @@ CALENDAR=CALENDAR.sort((a,b)=>a.timestamp - b.timestamp);
function getDate(timestamp) {
return new Date(timestamp*1000);
}
function formatDay(date) {
if (!settings.useToday) {
return Locale.date(date);
}
const dateformatted = date.toISOString().split('T')[0]; // yyyy-mm-dd
const today = new Date(Date.now()).toISOString().split('T')[0]; // yyyy-mm-dd
if (dateformatted == today) {
return /*LANG*/"Today ";
} else {
const tomorrow = new Date(Date.now() + 86400 * 1000).toISOString().split('T')[0]; // yyyy-mm-dd
if (dateformatted == tomorrow) {
return /*LANG*/"Tomorrow ";
}
return Locale.date(date);
}
}
function formatDateLong(date, includeDay, allDay) {
let shortTime = Locale.time(date,1)+Locale.meridian(date);
if(allDay) shortTime = "";
if(includeDay || allDay)
return Locale.date(date)+" "+shortTime;
if(includeDay || allDay) {
return formatDay(date)+" "+shortTime;
}
return shortTime;
}
function formatDateShort(date, allDay) {
return Locale.date(date).replace(/\d\d\d\d/,"")+(allDay?
"" : Locale.time(date,1)+Locale.meridian(date));
return formatDay(date).replace(/\d\d\d\d/,"")+(allDay?"":Locale.time(date,1)+Locale.meridian(date));
}
var lines = [];

View File

@ -1,7 +1,7 @@
{
"id": "agenda",
"name": "Agenda",
"version": "0.10",
"version": "0.11",
"description": "Simple agenda",
"icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],

View File

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

View File

@ -3,3 +3,5 @@
0.03: Do not load AGPS data on boot
Increase minimum interval to 6 hours
0.04: Write AGPS data chunks with delay to improve reliability
0.05: Show last success date
Do not start A-GPS update automatically

View File

@ -23,12 +23,26 @@ Bangle.drawWidgets();
let waiting = false;
function start() {
function start(restart) {
g.reset();
g.clear();
waiting = false;
display("Retry?", "touch to retry");
if (!restart) {
display("Start?", "touch to start");
}
else {
display("Retry?", "touch to retry");
}
Bangle.on("touch", () => { updateAgps(); });
const file = "agpsdata.json";
let data = require("Storage").readJSON(file, 1) || {};
if (data.lastUpdate) {
g.setFont("Vector", 11);
g.drawString("last success:", 5, g.getHeight() - 22);
g.drawString(new Date(data.lastUpdate).toISOString(), 5, g.getHeight() - 11);
}
}
function updateAgps() {
@ -36,7 +50,7 @@ function updateAgps() {
g.clear();
if (!waiting) {
waiting = true;
display("Updating A-GPS...", "takes ~ 10 seconds");
display("Updating A-GPS...", "takes ~10 seconds");
require("agpsdata").pull(function() {
waiting = false;
display("A-GPS updated.", "touch to close");
@ -45,10 +59,10 @@ function updateAgps() {
function(error) {
waiting = false;
E.showAlert(error, "Error")
.then(() => { start(); });
.then(() => { start(true); });
});
} else {
display("Waiting...");
}
}
updateAgps();
start(false);

View File

@ -2,7 +2,7 @@
"name": "A-GPS Data Downloader App",
"shortName":"A-GPS Data",
"icon": "agpsdata.png",
"version":"0.04",
"version":"0.05",
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"tags": "boot,tool,assisted,gps,agps,http",
"allow_emulator":true,

View File

@ -2,4 +2,5 @@
0.02: Design improvements and fixes.
0.03: Indicate battery level through line occurrence.
0.04: Use widget_utils module.
0.05: Support for clkinfo.
0.05: Support for clkinfo.
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.

View File

@ -29,7 +29,6 @@ var H = g.getHeight();
var cx = W/2;
var cy = H/2;
var drawTimeout;
var lock_input = false;
/************************************************
@ -93,28 +92,6 @@ menu = menu.concat(clockItems);
settings.menuPosY = 0;
}
// Set draw functions for each item
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
// For the clock, we have a special case, as we don't wanna redraw
// immediately when something changes. Instead, we update data each minute
// to save some battery etc. Therefore, we hide (and disable the listener)
// immedeately after redraw...
item.hide();
// After drawing the item, we enable inputs again...
lock_input = false;
var info = item.get();
drawMenuItem(info.text, info.img);
}
item.on('redraw', drawItem);
})
});
function canRunMenuItem(){
if(settings.menuPosY == 0){
return false;
@ -204,8 +181,6 @@ function drawMenuItem(text, image){
drawTime();
return
}
// image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==");
text = String(text);
g.reset().setBgColor("#fff").setColor("#000");
@ -292,7 +267,7 @@ function drawDigits(){
}
function drawDate(){
function drawMenu(){
var menuEntry = menu[settings.menuPosX];
// The first entry is the overview...
@ -302,9 +277,8 @@ function drawDate(){
}
// Draw item if needed
lock_input = true;
var item = menuEntry.items[settings.menuPosY-1];
item.show();
var item = menuEntry.items[settings.menuPosY-1].get();
drawMenuItem(item.text, item.img);
}
@ -320,7 +294,7 @@ function draw(){
g.setColor(1,1,1);
drawBackground();
drawDate();
drawMenu();
drawCircle(Bangle.isLocked());
}
@ -353,10 +327,6 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right && !is_upper && !is_lower;
var is_center = !is_upper && !is_lower && !is_left && !is_right;
if(lock_input){
return;
}
if(is_lower){
Bangle.buzz(40, 0.6);
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);

View File

@ -3,7 +3,7 @@
"name": "AI Clock",
"shortName":"AI Clock",
"icon": "aiclock.png",
"version":"0.05",
"version":"0.06",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",

View File

@ -37,3 +37,4 @@
0.34: Add "Confirm" option to alarm/timer edit menus
0.35: Add automatic translation of more strings
0.36: alarm widget moved out of app
0.37: add message input and dated Events

View File

@ -1,15 +1,18 @@
# Alarms & Timers
This app allows you to add/modify any alarms and timers.
This app allows you to add/modify any alarms, timers and events.
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
## Menu overview
- `New...`
- `New Alarm` &rarr; Configure a new alarm
- `New Alarm` &rarr; Configure a new alarm (triggered based on time and day of week)
- `Repeat` &rarr; Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
- `New Timer` &rarr; Configure a new timer
- `New Timer` &rarr; Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
- `New Event` &rarr; Configure a new event (triggered based on time and date)
- `Advanced`
- `Scheduler settings` &rarr; Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
- `Enable All` &rarr; Enable _all_ disabled alarms & timers

View File

@ -48,9 +48,10 @@ function showMainMenu() {
};
alarms.forEach((e, index) => {
var label = e.timer
var label = (e.timer
? require("time_utils").formatDuration(e.timer)
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
: (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
) + (e.msg ? " " + e.msg : "");
menu[label] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
@ -67,11 +68,12 @@ function showNewMenu() {
"": { "title": /*LANG*/"New..." },
"< Back": () => showMainMenu(),
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined),
/*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true)
});
}
function showEditAlarmMenu(selectedAlarm, alarmIndex) {
function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm();
@ -82,11 +84,16 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
}
var time = require("time_utils").decodeTime(alarm.t);
if (withDate && !alarm.date) alarm.date = new Date().toLocalISOString().slice(0,10);
var date = alarm.date ? new Date(alarm.date) : undefined;
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
var keyboard = "textinput";
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
const menu = {
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
"": { "title": title },
"< Back": () => {
prepareAlarmForSave(alarm, alarmIndex, time);
prepareAlarmForSave(alarm, alarmIndex, time, date);
saveAndReload();
showMainMenu();
},
@ -106,6 +113,36 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
wrap: true,
onchange: v => time.m = v
},
/*LANG*/"Day": {
value: date ? date.getDate() : null,
min: 1,
max: 31,
wrap: true,
onchange: v => date.setDate(v)
},
/*LANG*/"Month": {
value: date ? date.getMonth() + 1 : null,
format: v => require("date_utils").month(v),
onchange: v => date.setMonth((v+11)%12)
},
/*LANG*/"Year": {
value: date ? date.getFullYear() : null,
min: new Date().getFullYear(),
max: 2100,
onchange: v => date.setFullYear(v)
},
/*LANG*/"Message": {
value: alarm.msg,
onchange: () => {
setTimeout(() => {
keyboard.input({text:alarm.msg}).then(result => {
alarm.msg = result;
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
});
}, 100);
}
},
/*LANG*/"Enabled": {
value: alarm.on,
onchange: v => alarm.on = v
@ -115,8 +152,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
alarm.rp = repeat;
alarm.dow = dow;
alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
})
},
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
@ -136,6 +173,15 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
}
};
if (!keyboard) delete menu[/*LANG*/"Message"];
if (alarm.date || withDate) {
delete menu[/*LANG*/"Repeat"];
} else {
delete menu[/*LANG*/"Day"];
delete menu[/*LANG*/"Month"];
delete menu[/*LANG*/"Year"];
}
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
@ -145,7 +191,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
showMainMenu();
} else {
alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
}
});
};
@ -154,14 +200,17 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
E.showMenu(menu);
}
function prepareAlarmForSave(alarm, alarmIndex, time) {
function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
alarm.t = require("time_utils").encodeTime(time);
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
if(date) alarm.date = date.toLocalISOString().slice(0,10);
if (alarmIndex === undefined) {
alarms.push(alarm);
} else {
alarms[alarmIndex] = alarm;
if(!temp) {
if (alarmIndex === undefined) {
alarms.push(alarm);
} else {
alarms[alarmIndex] = alarm;
}
}
}
@ -255,6 +304,8 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
}
var time = require("time_utils").decodeTime(timer.timer);
var keyboard = "textinput";
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
const menu = {
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
@ -285,6 +336,18 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
wrap: true,
onchange: v => time.s = v
},
/*LANG*/"Message": {
value: timer.msg,
onchange: () => {
setTimeout(() => {
keyboard.input({text:timer.msg}).then(result => {
timer.msg = result;
prepareTimerForSave(timer, timerIndex, time, true);
setTimeout(showEditTimerMenu, 10, timer, timerIndex);
});
}, 100);
}
},
/*LANG*/"Enabled": {
value: timer.on,
onchange: v => timer.on = v
@ -306,6 +369,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
}
};
if (!keyboard) delete menu[/*LANG*/"Message"];
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
@ -324,15 +388,17 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
E.showMenu(menu);
}
function prepareTimerForSave(timer, timerIndex, time) {
function prepareTimerForSave(timer, timerIndex, time, temp) {
timer.timer = require("time_utils").encodeTime(time);
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
timer.last = 0;
if (timerIndex === undefined) {
alarms.push(timer);
} else {
alarms[timerIndex] = timer;
if (!temp) {
if (timerIndex === undefined) {
alarms.push(timer);
} else {
alarms[timerIndex] = timer;
}
}
}

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.36",
"version": "0.37",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",

View File

@ -18,3 +18,4 @@
0.18: Use new message library
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
0.19: Add automatic translation for a couple of strings.
0.20: Fix wrong event used for forwarded GPS data from Gadgetbridge and add mapper to map longitude value correctly.

View File

@ -134,7 +134,10 @@
event.satellites = NaN;
event.course = NaN;
event.fix = 1;
Bangle.emit('gps', event);
event.lon = event.long;
delete event.long;
Bangle.emit('GPS', event);
},
"is_gps_active": function() {
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
@ -208,7 +211,7 @@
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
Bangle.setGPSPower = (isOn, appID) => {
// if not connected, use old logic
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
// Emulate old GPS power logic
if (!Bangle._PWR) Bangle._PWR={};
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.19",
"version": "0.20",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -22,3 +22,6 @@
0.22: Use the new clkinfo module for the menu.
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
0.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.

View File

@ -1,3 +1,5 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears
/************************************************
* Includes
*/
@ -12,8 +14,6 @@ const clock_info = require("clock_info");
const SETTINGS_FILE = "bwclk.setting.json";
const W = g.getWidth();
const H = g.getHeight();
var lock_input = false;
/************************************************
* Settings
@ -28,7 +28,7 @@ let settings = {
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
settings[key] = saved_settings[key];
}
/************************************************
@ -74,20 +74,20 @@ Graphics.prototype.setMiniFont = function(scale) {
return this;
};
function imgLock(){
let imgLock = function() {
return {
width : 16, height : 16, bpp : 1,
transparent : 0,
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
}
}
};
};
/************************************************
* Menu
*/
// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
var bwItems = {
let bwItems = {
name: null,
img: null,
items: [
@ -99,7 +99,7 @@ var bwItems = {
]
};
function weekOfYear() {
let weekOfYear = function() {
var date = new Date();
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
@ -109,11 +109,11 @@ function weekOfYear() {
// 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();
let menu = clock_info.load();
menu = menu.concat(bwItems);
@ -123,29 +123,7 @@ if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPos
settings.menuPosY = 0;
}
// Set draw functions for each item
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
// For the clock, we have a special case, as we don't wanna redraw
// immediately when something changes. Instead, we update data each minute
// to save some battery etc. Therefore, we hide (and disable the listener)
// immedeately after redraw...
item.hide();
// After drawing the item, we enable inputs again...
lock_input = false;
var info = item.get();
drawMenuItem(info.text, info.img);
}
item.on('redraw', drawItem);
})
});
function canRunMenuItem(){
let canRunMenuItem = function() {
if(settings.menuPosY == 0){
return false;
}
@ -153,10 +131,10 @@ function canRunMenuItem(){
var menuEntry = menu[settings.menuPosX];
var item = menuEntry.items[settings.menuPosY-1];
return item.run !== undefined;
}
};
function runMenuItem(){
let runMenuItem = function() {
if(settings.menuPosY == 0){
return;
}
@ -171,13 +149,13 @@ function runMenuItem(){
} catch (ex) {
// Simply ignore it...
}
}
};
/************************************************
* Draw
*/
function draw() {
let draw = function() {
// Queue draw again
queueDraw();
@ -186,10 +164,10 @@ function draw() {
drawMenuAndTime();
drawLock();
drawWidgets();
}
};
function drawDate(){
let drawDate = function() {
// Draw background
var y = H/5*2 + (isFullscreen() ? 0 : 8);
g.reset().clearRect(0,0,W,y);
@ -216,10 +194,10 @@ function drawDate(){
g.setMediumFont();
g.setColor(g.theme.fg);
g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
}
};
function drawTime(y, smallText){
let drawTime = function(y, smallText) {
// Draw background
var date = new Date();
@ -244,9 +222,9 @@ function drawTime(y, smallText){
g.setLargeFont();
}
g.drawString(timeStr, W/2, y);
}
};
function drawMenuItem(text, image){
let drawMenuItem = function(text, image) {
// First clear the time region
var y = H/5*2 + (isFullscreen() ? 0 : 8);
@ -279,10 +257,10 @@ function drawMenuItem(text, image){
// Draw time
drawTime(y, hasText);
}
};
function drawMenuAndTime(){
let drawMenuAndTime = function() {
var menuEntry = menu[settings.menuPosX];
// The first entry is the overview...
@ -292,37 +270,35 @@ function drawMenuAndTime(){
}
// Draw item if needed
lock_input = true;
var item = menuEntry.items[settings.menuPosY-1];
item.show();
}
var item = menuEntry.items[settings.menuPosY-1].get();
drawMenuItem(item.text, item.img);
};
function drawLock(){
let drawLock = function() {
if(settings.showLock && Bangle.isLocked()){
g.setColor(g.theme.fg);
g.drawImage(imgLock(), W-16, 2);
}
}
};
function drawWidgets(){
let drawWidgets = function() {
if(isFullscreen()){
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} else {
Bangle.drawWidgets();
}
}
};
function isFullscreen(){
let isFullscreen = function() {
var s = settings.screen.toLowerCase();
if(s == "dynamic"){
return Bangle.isLocked()
return Bangle.isLocked();
} else {
return s == "full"
return s == "full";
}
}
};
@ -330,29 +306,30 @@ function isFullscreen(){
* Listener
*/
// timeout used to update every minute
var drawTimeout;
let drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
let queueDraw = function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
};
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
let lcdListenerBw = function(on) {
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
};
Bangle.on('lcdPower', lcdListenerBw);
Bangle.on('lock', function(isLocked) {
let lockListenerBw = function(isLocked) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
@ -363,9 +340,10 @@ Bangle.on('lock', function(isLocked) {
}
draw();
});
};
Bangle.on('lock', lockListenerBw);
Bangle.on('charging',function(charging) {
let chargingListenerBw = function(charging) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
@ -373,9 +351,10 @@ Bangle.on('charging',function(charging) {
settings.menuPosX = 0;
settings.menuPosY = 1;
draw();
});
};
Bangle.on('charging', chargingListenerBw);
Bangle.on('touch', function(btn, e){
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;
@ -388,10 +367,6 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right && !is_upper && !is_lower;
var is_center = !is_upper && !is_lower && !is_left && !is_right;
if(lock_input){
return;
}
if(is_lower){
Bangle.buzz(40, 0.6);
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
@ -431,17 +406,11 @@ Bangle.on('touch', function(btn, e){
runMenuItem();
}
}
});
E.on("kill", function(){
try{
storage.write(SETTINGS_FILE, settings);
} catch(ex){
// If this fails, we still kill the app...
}
});
};
Bangle.on('touch', touchListenerBw);
let save = () => storage.write(SETTINGS_FILE, settings);
E.on("kill", save);
/************************************************
* Startup Clock
@ -450,10 +419,26 @@ E.on("kill", function(){
// The upper part is inverse i.e. light if dark and dark if light theme
// is enabled. In order to draw the widgets correctly, we invert the
// dark/light theme as well as the colors.
let themeBackup = g.theme;
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.setUI({
mode : "clock",
remove : function() {
// Called to unload all of the clock app
Bangle.removeListener('lcdPower', lcdListenerBw);
Bangle.removeListener('lock', lockListenerBw);
Bangle.removeListener('charging', chargingListenerBw);
Bangle.removeListener('touch', touchListenerBw);
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
// save settings
save();
E.removeListener("kill", save);
g.setTheme(themeBackup);
}
});
// Load widgets and draw clock the first time
Bangle.loadWidgets();
@ -464,3 +449,5 @@ for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
// Draw first time
draw();
} // End of app scope

View File

@ -1,7 +1,7 @@
{
"id": "bwclk",
"name": "BW Clock",
"version": "0.24",
"version": "0.25",
"description": "A very minimalistic clock to mainly show date and time.",
"readme": "README.md",
"icon": "app.png",

View File

@ -39,3 +39,4 @@
0.21: Remade all icons without a palette for dark theme
Now re-adds widgets if they were hidden when fast-loading
0.22: Fixed crash if item has no image and cutting long overflowing text
0.23: Setting circles colours per clkinfo and not position

View File

@ -20,24 +20,12 @@ let settings = Object.assign(
storage.readJSON("circlesclock.default.json", true) || {},
storage.readJSON(SETTINGS_FILE, true) || {}
);
//TODO deprecate this (and perhaps use in the clkinfo module)
// Load step goal from health app and pedometer widget as fallback
if (settings.stepGoal == undefined) {
let d = storage.readJSON("health.json", true) || {};
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
if (settings.stepGoal == undefined) {
d = storage.readJSON("wpedom.json", true) || {};
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
}
}
let drawTimeout;
const showWidgets = settings.showWidgets || false;
const circleCount = settings.circleCount || 3;
const showBigWeather = settings.showBigWeather || false;
let hrtValue; //TODO deprecate this
let now = Math.round(new Date().getTime() / 1000);
// layout values:
@ -128,8 +116,11 @@ let draw = function() {
queueDraw();
}
let getCircleColor = function(index) {
let color = settings["circle" + index + "color"];
let getCircleColor = function(item, clkmenu) {
let colorKey = clkmenu.name;
if(!clkmenu.dynamic) colorKey += "/"+item.name;
colorKey += "_color";
let color = settings[colorKey];
if (color && color != "") return color;
return g.theme.fg;
}
@ -138,7 +129,7 @@ let getGradientColor = function(color, percent) {
if (isNaN(percent)) percent = 0;
if (percent > 1) percent = 1;
let colorList = [
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000'
'#00ff00', '#80ff00', '#ffff00', '#ff8000', '#ff0000'
];
if (color == "fg") {
color = colorFg;
@ -151,6 +142,17 @@ let getGradientColor = function(color, percent) {
let colorIndex = colorList.length - Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
}
colorList = [
'#0000ff', '#8800ff', '#ff00ff', '#ff0088', '#ff0000'
];
if (color == "blue-red") {
let colorIndex = Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#0000ff";
}
if (color == "red-blue") {
let colorIndex = colorList.length - Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
}
return color;
}
@ -172,10 +174,10 @@ let drawEmpty = function(img, w, color) {
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
}
let drawCircle = function(index, item, data) {
let drawCircle = function(index, item, data, clkmenu) {
var w = circlePosX[index-1];
drawCircleBackground(w);
const color = getCircleColor(index);
const color = getCircleColor(item, clkmenu);
//drawEmpty(info? info.img : null, w, color);
var img = data.img;
var percent = 1; //fill up if no range
@ -338,7 +340,8 @@ Bangle.setUI({
let clockInfoDraw = (itm, info, options) => {
//print("Draw",itm.name,options);
drawCircle(options.circlePosition, itm, info);
let clkmenu = clockInfoItems[options.menuA];
drawCircle(options.circlePosition, itm, info, clkmenu);
if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1)
};
let clockInfoItems = require("clock_info").load();

View File

@ -3,23 +3,21 @@
"showWidgets": false,
"weatherCircleData": "humidity",
"circleCount": 3,
"circle1color": "green-red",
"circle2color": "#0000ff",
"circle3color": "red-green",
"circle4color": "#ffff00",
"Bangle/Battery_color":"red-green",
"Bangle/Steps_color":"#0000ff",
"Bangle/HRM_color":"green-red",
"Bangle/Altitude_color":"#00ff00",
"Weather/conditionWithTemperature_color":"#ffff00",
"Weather/condition_color":"#00ffff",
"Weather/humidity_color":"#00ffff",
"Weather/wind_color":"fg",
"Weather/temperature_color":"blue-red",
"Alarms_color":"#00ff00",
"Agenda_color":"#ff0000",
"circle1colorizeIcon": true,
"circle2colorizeIcon": true,
"circle3colorizeIcon": true,
"circle4colorizeIcon": false,
"updateInterval": 60,
"showBigWeather": false,
"minHR": 40,
"maxHR": 200,
"confidence": 0,
"stepGoal": 10000,
"stepDistanceGoal": 8000,
"stepLength": 0.8,
"hrmValidity": 60
"showBigWeather": false
}

View File

@ -1,7 +1,7 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
"version":"0.22",
"version":"0.23",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],

View File

@ -12,12 +12,10 @@
storage.write(SETTINGS_FILE, settings);
}
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
"cyan", "white", "black", "green->red", "red->green", "foreground"];
const weatherData = ["empty", "humidity", "wind"];
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
"#00ffff", "#fff", "#000", "green-red", "red-green", "blue-red", "red-blue", "fg"];
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
"cyan", "white", "black", "green->red", "red->green", "blue->red", "red->blue", "foreground"];
function showMainMenu() {
let menu ={
@ -30,31 +28,11 @@
step: 1,
onchange: x => save('circleCount', x),
},
/*LANG*/'circle 1': ()=>showCircleMenu(1),
/*LANG*/'circle 2': ()=>showCircleMenu(2),
/*LANG*/'circle 3': ()=>showCircleMenu(3),
/*LANG*/'circle 4': ()=>showCircleMenu(4),
/*LANG*/'battery warn': {
value: settings.batteryWarn,
min: 10,
max : 100,
step: 10,
format: x => {
return x + '%';
},
onchange: x => save('batteryWarn', x),
},
/*LANG*/'show widgets': {
value: !!settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: x => save('showWidgets', x),
},
/*LANG*/'weather data': {
value: weatherData.indexOf(settings.weatherCircleData),
min: 0, max: 2,
format: v => weatherData[v],
onchange: x => save('weatherCircleData', weatherData[x]),
},
/*LANG*/'update interval': {
value: settings.updateInterval,
min: 0,
@ -65,41 +43,54 @@
},
onchange: x => save('updateInterval', x),
},
//TODO deprecated local icons, may disappear in future
/*LANG*/'legacy weather icons': {
value: !!settings.legacyWeatherIcons,
format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'),
onchange: x => save('legacyWeatherIcons', x),
},
/*LANG*/'show big weather': {
value: !!settings.showBigWeather,
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
onchange: x => save('showBigWeather', x),
}
},
/*LANG*/'colorize icons': ()=>showCircleMenus()
};
clock_info.load().forEach(e=>{
if(e.dynamic) {
const colorKey = e.name + "_color";
menu[e.name+/*LANG*/' color'] = {
value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1,
format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]),
};
} else {
let values = e.items.map(i=>e.name+"/"+i.name);
let names = e.name=="Bangle" ? e.items.map(i=>i.name) : values;
values.forEach((v,i)=>{
const colorKey = v + "_color";
menu[names[i]+/*LANG*/' color'] = {
value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1,
format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]),
};
});
}
})
E.showMenu(menu);
}
function showCircleMenu(circleId) {
const circleName = "circle" + circleId;
const colorKey = circleName + "color";
const colorizeIconKey = circleName + "colorizeIcon";
const menu = {
'': { 'title': /*LANG*/'Circle ' + circleId },
/*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'color': {
value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1,
format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]),
},
/*LANG*/'colorize icon': {
function showCircleMenus() {
const menu = {
'': { 'title': /*LANG*/'Colorize icons'},
/*LANG*/'< Back': ()=>showMainMenu(),
};
for(var circleId=1; circleId<=4; ++circleId) {
const circleName = "circle" + circleId;
const colorKey = circleName + "color";
const colorizeIconKey = circleName + "colorizeIcon";
menu[/*LANG*/'circle ' + circleId] = {
value: settings[colorizeIconKey] || false,
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'),
onchange: x => save(colorizeIconKey, x),
},
};
format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
onchange: x => save(colorizeIconKey, x),
};
}
E.showMenu(menu);
}

View File

@ -4,3 +4,4 @@
0.04: Now displays the opened text string at launch.
0.05: Now scrolls text when string gets longer than screen width.
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
0.07: Settings for display colors

View File

@ -12,5 +12,8 @@ Known bugs:
- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes.
- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case.
To do:
- Possibly provide a dragboard.settings.js file
Settings:
- CAPS LOCK: all characters are displayed and typed in uppercase
- ABC Color: color of the characters row
- Num Color: color of the digits and symbols row
- Highlight Color: color of the currently highlighted character

View File

@ -2,12 +2,14 @@ exports.input = function(options) {
options = options||{};
var text = options.text;
if ("string"!=typeof text) text="";
let settings = require('Storage').readJSON('dragboard.json',1)||{}
var R = Bangle.appRect;
const paramToColor = (param) => g.toColor(`#${settings[param].toString(16).padStart(3,0)}`);
var BGCOLOR = g.theme.bg;
var HLCOLOR = g.theme.fg;
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
var NUMCOLOR = g.toColor(0,1,0);//'#00FF00';
var HLCOLOR = settings.Highlight ? paramToColor("Highlight") : g.theme.fg;
var ABCCOLOR = settings.ABC ? paramToColor("ABC") : g.toColor(1,0,0);//'#FF0000';
var NUMCOLOR = settings.Num ? paramToColor("Num") : g.toColor(0,1,0);//'#00FF00';
var BIGFONT = '6x8:3';
var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1)));
var SMALLFONT = '6x8:1';
@ -102,6 +104,7 @@ exports.input = function(options) {
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
function changeCase(abcHL) {
if (settings.uppercase) return;
g.setColor(BGCOLOR);
g.setFontAlign(-1, -1, 0);
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);

View File

@ -1,6 +1,6 @@
{ "id": "dragboard",
"name": "Dragboard",
"version":"0.06",
"version":"0.07",
"description": "A library for text input via swiping keyboard",
"icon": "app.png",
"type":"textinput",
@ -9,6 +9,7 @@
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"textinput","url":"lib.js"}
{"name":"textinput","url":"lib.js"},
{"name":"dragboard.settings.js","url":"settings.js"}
]
}

View File

@ -0,0 +1,48 @@
(function(back) {
let settings = require('Storage').readJSON('dragboard.json',1)||{};
const colors = {
4095: /*LANG*/"White",
4080: /*LANG*/"Yellow",
3840: /*LANG*/"Red",
3855: /*LANG*/"Magenta",
255: /*LANG*/"Cyan",
240: /*LANG*/"Green",
15: /*LANG*/"Blue",
0: /*LANG*/"Black",
'-1': /*LANG*/"Default"
};
const save = () => require('Storage').write('dragboard.json', settings);
function colorMenu(key) {
let menu = {'': {title: key}, '< Back': () => E.showMenu(appMenu)};
Object.keys(colors).forEach(color => {
var label = colors[color];
menu[label] = {
value: settings[key] == color,
onchange: () => {
if (color >= 0) {
settings[key] = color;
} else {
delete settings[key];
}
save();
setTimeout(E.showMenu, 10, appMenu);
}
};
});
return menu;
}
const appMenu = {
'': {title: 'Dragboard'}, '< Back': back,
/*LANG*/'CAPS LOCK': {
value: !!settings.uppercase,
onchange: v => {settings.uppercase = v; save();}
},
/*LANG*/'ABC Color': () => E.showMenu(colorMenu("ABC")),
/*LANG*/'Num Color': () => E.showMenu(colorMenu("Num")),
/*LANG*/'Highlight Color': () => E.showMenu(colorMenu("Highlight"))
};
E.showMenu(appMenu);
});

View File

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

View File

@ -0,0 +1,20 @@
# Gemini clock
A simple clock face using the Buro Destruct Geminis font, inspired by their Pebble Watch designs: https://burodestruct.net/work/pebble-watchfaces
![image](watch-in-use.jpg)
It is designed for maximum legibility and utility whilst still showing widgets.
If editing or remixing this code, please retain leading zeroes on the hours, they are an integral part of the design.
The minutes are not right-aligned deliberately so that the numbers don't jump around too much when they change.
## Creator
Created by Giles Booth:
- http://www.suppertime.co.uk/blogmywiki/
- https://mastodon.social/@blogmywiki
- https://github.com/blogmywiki

View File

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

BIN
apps/geminiclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
{ "id": "geminiclock",
"name": "Gemini clock",
"shortName":"Gemini Clock",
"icon": "app.png",
"version":"0.01",
"description": "Watch face using retro Gemini font",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"geminiclock.app.js","url":"gemini-watch-app.js"},
{"name":"geminiclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -7,3 +7,4 @@
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
0.09: Fix FIFO_FULL error
0.10: Show satellites "in view" separated by GNS-system
0.11: Show number of packets received

View File

@ -5,7 +5,7 @@ function satelliteImage() {
var Layout = require("Layout");
var layout;
//Bangle.setGPSPower(1, "app");
E.showMessage(/*LANG*/"Loading..."); // avoid showing rubbish on screen
E.showMessage(/*LANG*/"Waiting for GNS data..."); // avoid showing rubbish on screen
var lastFix = {
fix: -1,
@ -19,6 +19,7 @@ var lastFix = {
var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0;
const leaveNofixLayout = 1; // 0 = stay on initial screen for debugging (default = 1)
var listenerGPSraw = 0;
var dataCounter = 0;
function formatTime(now) {
if (now == undefined) {
@ -80,11 +81,15 @@ function onGPS(fix) {
type:"v", c: [
{type:"txt", font:"6x8:2", label:"GPS Info" },
{type:"img", src:satelliteImage, pad:4 },
{type:"txt", font:"6x8", label:"Waiting for GPS" },
{type:"txt", font:"6x8", label:"Waiting for GPS fix" },
{type:"h", c: [
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
{type:"txt", font:"6x8", pad:3, label:"Satellites used" }
]},
{type:"h", c: [
{type:"txt", font:"10%", label:dataCounter, pad:2, id:"dataCounter" },
{type:"txt", font:"6x8", pad:3, label:"packets received" }
]},
{type:"txt", font:"6x8", label:"", fillx:true, id:"progress" }
]},{lazy:false});
}
@ -122,6 +127,9 @@ function onGPS(fix) {
layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL;
// console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL);
layout.render(layout.progress);
layout.clear(layout.dataCounter);
layout.dataCounter.label = ++dataCounter;
layout.render(layout.dataCounter);
}
}

View File

@ -1,11 +1,11 @@
{
"id": "gpsinfo",
"name": "GPS Info",
"version": "0.10",
"description": "An application that displays information about altitude, lat/lon, satellites and time",
"version": "0.11",
"description": "An application that displays information about latitude, longitude, altitude, speed, satellites and time",
"icon": "gps-info.png",
"type": "app",
"tags": "gps,outdoors",
"tags": "gps,outdoors,tools",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"gpsinfo.app.js","url":"gps-info.js"},

View File

@ -16,3 +16,4 @@
0.15: Fix charts (fix #1366)
0.16: Code tidyup, add back button in top left of health app graphs
0.17: Add automatic translation of bar chart labels
0.18: Show step goal in daily step chart

View File

@ -1,6 +1,6 @@
# Health Tracking
Logs health data to a file every 10 minutes, and provides an app to view it
Logs health data to a file in a defined interval, and provides an app to view it
**BETA - requires firmware 2v11 or later**
@ -22,6 +22,7 @@ Stores:
* **Heart Rt** - Whether to monitor heart rate or not
* **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget
* **3 Min** - Turn HRM on every 3 minutes (for each heath entry) and turn it off after 1 minute, or when a good reading is found
* **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found
* **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours)
* **Daily Step Goal** - Default 10000, daily step goal for pedometer apps to use and for the step goal notification
@ -50,3 +51,7 @@ and run `EspruinoDocs/bin/minify.js lib.js lib.min.js`
* Yearly view
* Heart rate 'zone' graph
* .. other
## License
The graphs on the web interface use Chart.js, licensed under MIT License.

View File

@ -38,6 +38,7 @@ function menuHRM() {
function stepsPerHour() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "stepsPerHour";
var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.steps);
setButton(menuStepCount);
@ -46,14 +47,17 @@ function stepsPerHour() {
function stepsPerDay() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "stepsPerDay";
var data = new Uint16Array(31);
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps);
setButton(menuStepCount);
barChart(/*LANG*/"DAY", data);
drawHorizontalLine(settings.stepGoal);
}
function hrmPerHour() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "hrmPerHour";
var data = new Uint16Array(24);
var cnt = new Uint8Array(23);
require("health").readDay(new Date(), h=>{
@ -67,6 +71,7 @@ function hrmPerHour() {
function hrmPerDay() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "hrmPerDay";
var data = new Uint16Array(31);
var cnt = new Uint8Array(31);
require("health").readDailySummaries(new Date(), h=>{
@ -80,6 +85,7 @@ function hrmPerDay() {
function movementPerHour() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "movementPerHour";
var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.movement);
setButton(menuMovement);
@ -88,6 +94,7 @@ function movementPerHour() {
function movementPerDay() {
E.showMessage(/*LANG*/"Loading...");
current_selection = "movementPerDay";
var data = new Uint16Array(31);
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement);
setButton(menuMovement);
@ -97,12 +104,14 @@ function movementPerDay() {
// Bar Chart Code
const w = g.getWidth();
const h = g.getHeight();
const bar_bot = 140;
var data_len;
var chart_index;
var chart_max_datum;
var chart_label;
var chart_data;
var current_selection;
// find the max value in the array, using a loop due to array size
function max(arr) {
@ -131,7 +140,6 @@ function barChart(label, dt) {
}
function drawBarChart() {
const bar_bot = 140;
const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre
var bar_top;
var bar;
@ -157,6 +165,11 @@ function drawBarChart() {
}
}
function drawHorizontalLine(value) {
const top = bar_bot - 100 * value / chart_max_datum;
g.setColor(g.theme.fg).drawLine(0, top ,g.getWidth(), top);
}
function setButton(fn) {
Bangle.setUI({mode:"custom",
back:fn,
@ -170,9 +183,13 @@ function setButton(fn) {
return fn();
}
drawBarChart();
if (current_selection == "stepsPerDay") {
drawHorizontalLine(settings.stepGoal);
}
}});
}
Bangle.loadWidgets();
Bangle.drawWidgets();
var settings = require("Storage").readJSON("health.json",1)||{};
menuMain();

14
apps/health/chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,9 +3,10 @@
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="table"></div>
<div id="content"></div>
<script src="../../core/lib/interface.js"></script>
<script type="module" src="chart.min.js"></script>
<script>
const DB_RECORD_LEN = 4;
const DB_RECORDS_PER_HR = 6;
@ -14,7 +15,7 @@ const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31;
const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
var domTable = document.getElementById("table");
var domContent = document.getElementById("content");
function saveCSV(data, date, title) {
// date = "2021-9"/ etc
@ -59,7 +60,7 @@ function downloadHealth(filename, callback) {
}
function getMonthList() {
Util.showModal("Loading...");
domTable.innerHTML = "";
domContent.innerHTML = "";
Puck.eval(`require("Storage").list(/^health-.*\\.raw$/)`,files=>{
files = files.map(f => {
var m = f.match(/^health-([^\.]+)\.raw$/);
@ -69,7 +70,7 @@ function getMonthList() {
str : new Date(m[1]).toLocaleString(undefined, {month:'long',year:'numeric'})
}
})
var html = `<table class="table table-striped table-hover">
var htmlOverview = `<table class="table table-striped table-hover">
<thead>
<tr>
<th>Month</th>
@ -78,36 +79,39 @@ function getMonthList() {
</thead>
<tbody>\n`;
files.forEach(f => {
html += `
htmlOverview += `
<tr>
<td>${f.str}</td>
<td>
<button class="btn btn-primary" filename="${f.filename}" date="${f.date}" task="downloadcsv">Download CSV</button>
<button class="btn btn-default" filename="${f.filename}" date="${f.date}" task="delete">Delete</button>
<button class="btn btn-primary" filename="${f.filename}" date="${f.date}" monthstr="${f.str}" task="monthtable">Table</button>
<button class="btn btn-primary" filename="${f.filename}" date="${f.date}" monthstr="${f.str}" task="monthgraph">Graph</button>
<button class="btn btn-error" filename="${f.filename}" date="${f.date}" task="delete" style="float: right;margin-right: 5px;">Delete</button>
</td>
</tr>
`;
});
if (files.length==0) {
html += `
htmlOverview += `
<tr>
<td>No data recorded</td>
<td></td>
</tr>
`;
}
html += `
htmlOverview += `
</tbody>
</table>`;
domTable.innerHTML = html;
domContent.innerHTML = htmlOverview;
Util.hideModal();
var buttons = domTable.querySelectorAll("button");
var buttons = domContent.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var filename = button.getAttribute("filename");
var date = button.getAttribute("date");
if (!filename || !date) return;
var monthstr = button.getAttribute("monthstr");
var task = button.getAttribute("task");
if (task=="delete") {
Util.showModal("Deleting...");
@ -119,11 +123,179 @@ function getMonthList() {
if (task=="downloadcsv") {
downloadHealth(filename, data => saveCSV(data, date, `Bangle.js Health ${date}`));
}
if (task=="monthtable") {
viewMonthDataAsTable(filename, date, monthstr);
}
if (task=="monthgraph") {
viewMonthDataAsGraph(filename, date, monthstr);
}
});
}
})
}
function getDailyData(data) {
var dailyData = [];
var idx = DB_HEADER_LEN;
for (var day = 0; day < 31; day++) {
var dayData = {steps: 0, bpm: 0, movement: 0};
for (var hr = 0; hr < 24; hr++) { // actually 25, see below
for (var m = 0; m < DB_RECORDS_PER_HR; m++) {
var h = data.substr(idx, DB_RECORD_LEN);
if (h != "\xFF\xFF\xFF\xFF") {
var h = {
day : day + 1,
hr : hr,
min : m * 10,
steps : (h.charCodeAt(0) << 8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
};
dayData.steps += h.steps; // sum
dayData.bpm = (dayData.bpm + h.bpm) / 2; // average
dayData.movement += h.movement; // sum
}
idx += DB_RECORD_LEN;
}
}
idx += DB_RECORD_LEN; // +1 because we have an extra record with totals
// for the end of the day
dailyData[day + 1] = dayData;
}
return dailyData;
}
function viewMonthDataAsTable(filename, date, monthstr) {
Util.showModal("Reading Health info...");
Util.readStorage(
filename, data => {
Util.hideModal();
var htmlOverview = `<h1>` + monthstr + `</ h1>
<button class="btn btn-primary" id="backtomonth" style="float: right;margin-right: 5px;">Back</button>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Day</th>
<th>Steps</th>
<th>BPM</th>
<th>Movement</th>
</tr>
</thead>
<tbody>\n`;
var dailyData = getDailyData(data);
for (var i = 1; i < dailyData.length + 1; i++) {
var dayData = dailyData[i];
if (dayData) {
htmlOverview += `<tr>
<td>${i}</td>
<td>${dayData.steps}</td>
<td>${Math.round(dayData.bpm)}</td>
<td>${dayData.movement}</td></tr>`
}
}
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#backtomonth").addEventListener("click",event => {
getMonthList();
});
});
}
function viewMonthDataAsGraph(filename, date, monthstr) {
Util.showModal("Reading Health info...");
Util.readStorage(
filename, data => {
Util.hideModal();
var html = `<h1>` + monthstr + `</ h1>
<button class="btn btn-primary" id="backtomonth" style="float: right;margin-right: 5px;">Back</button>
<h2>Steps</h2>
<canvas id="chartSteps"></canvas>
<h2>BPM</h2>
<canvas id="chartBPM"></canvas>
<h2>Movement</h2>
<canvas id="chartMovement"></canvas>`
domContent.innerHTML = html;
domContent.querySelector("#backtomonth").addEventListener("click",event => {
getMonthList();
});
var labels = [];
var dataSteps = [], dataBPM = [], dataMovement = [];
var dailyData = getDailyData(data);
for (var i = 1; i < dailyData.length + 1; i++) {
var dayData = dailyData[i];
if (dayData) {
labels.push(i);
dataSteps.push(dayData.steps);
dataBPM.push(dayData.bpm);
dataMovement.push(dayData.movement);
}
}
new Chart(document.getElementById("chartSteps"), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '# of steps',
data: dataSteps,
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
new Chart(document.getElementById("chartBPM"), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Beats per minute',
data: dataBPM,
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
new Chart(document.getElementById("chartMovement"), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Movement',
data: dataMovement,
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
}
function onInit() {
getMonthList();
}

View File

@ -60,7 +60,7 @@ exports.readDailySummaries = function(d, cb) {
}
}
// Read all records from the given month
// Read all records from the given day
exports.readDay = function(d, cb) {
var rec = getRecordIdx(d);
var fn = getRecordFN(d);

View File

@ -2,7 +2,7 @@
"id": "health",
"name": "Health Tracking",
"shortName": "Health",
"version": "0.17",
"version": "0.18",
"description": "Logs health data and provides an app to view it",
"icon": "app.png",
"tags": "tool,system,health",

View File

@ -2,6 +2,7 @@
"id": "infoclk",
"name": "Informational clock",
"version": "0.08",
"dependencies": {"weather":"app"},
"description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked",
"readme": "README.md",
"icon": "icon.png",
@ -10,7 +11,6 @@
"supports": [
"BANGLEJS2"
],
"allow_emulator": true,
"storage": [
{
"name": "infoclk.app.js",
@ -35,4 +35,4 @@
"name": "infoclk.json"
}
]
}
}

View File

@ -20,5 +20,6 @@
0.20: Use alarm for alarm functionality instead of own implementation.
0.21: Add custom theming.
0.22: Fix alarm and add build in function for step counting.
0.23: Add warning for low flash memory
0.24: Add ability to disable alarm functionality
0.23: Add warning for low flash memory.
0.24: Add ability to disable alarm functionality.
0.25: Add more colors to the settings and add the ability to disable the data charts+Markup.

View File

@ -19,6 +19,8 @@ the "sched" app must be installed on your device.
* The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen.
* Customizable theming colors in the settings menu of the app.
* Enable or disable the alarm feature.
* Enable or disbale the graphs for steps + hrm.
## Data that can be configured
* Steps - Steps loaded via the wpedom app.

View File

@ -13,6 +13,7 @@ let settings = {
themeColor2BG: "#FF00DC",
themeColor3BG: "#0094FF",
disableAlarms: false,
disableData: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
@ -55,7 +56,7 @@ function convert24to16(input)
return "0x"+RGB565.toString(16);
}
var color1C = convert24to16(color1);
var color1C = convert24to16(color1);//Converting colors to the correct format.
var color2C = convert24to16(color2);
var color3C = convert24to16(color3);
@ -63,7 +64,7 @@ var color3C = convert24to16(color3);
* Requirements and globals
*/
var colorPalette = new Uint16Array([
var colorPalette = new Uint16Array([//Used to change the color of the image if the user selects a color that is diffrent than the default.
0x0000, // not used
color2C, // second
color3C, // third
@ -708,18 +709,20 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right;
var is_upper = e.y < upper;
var is_lower = e.y > lower;
if(!settings.disableData){
if(is_left && lcarsViewPos == 1){
feedback();
lcarsViewPos = 0;
draw();
return;
if(is_left && lcarsViewPos == 1){
feedback();
lcarsViewPos = 0;
draw();
return;
} else if(is_right && lcarsViewPos == 0){
feedback();
lcarsViewPos = 1;
draw();
return;
} else if(is_right && lcarsViewPos == 0){
feedback();
lcarsViewPos = 1;
draw();
return;
}
}
if(lcarsViewPos == 0){

View File

@ -14,6 +14,7 @@
themeColor2BG: "#FF00DC",
themeColor3BG: "#0094FF",
disableAlarms: false,
disableData: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
@ -27,8 +28,8 @@
var dataOptions = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"];
var speedOptions = ["kph", "mph"];
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White'];
var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF'];
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green'];
var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F'];
E.showMenu({
'': { 'title': 'LCARS Clock' },
@ -79,7 +80,7 @@
},
'Theme Color 1': {
value: 0 | bg_code.indexOf(settings.themeColor1BG),
min: 0, max: 7,
min: 0, max: 11,
format: v => color_options[v],
onchange: v => {
settings.themeColor1BG = bg_code[v];
@ -88,7 +89,7 @@
},
'Theme Color 2': {
value: 0 | bg_code.indexOf(settings.themeColor2BG),
min: 0, max: 7,
min: 0, max: 11,
format: v => color_options[v],
onchange: v => {
settings.themeColor2BG = bg_code[v];
@ -97,7 +98,7 @@
},
'Theme Color 3': {
value: 0 | bg_code.indexOf(settings.themeColor3BG),
min: 0, max: 7,
min: 0, max: 11,
format: v => color_options[v],
onchange: v => {
settings.themeColor3BG = bg_code[v];
@ -112,5 +113,13 @@
save();
},
},
'Disable data pages functionality': {
value: settings.disableData,
format: () => (settings.disableData ? 'Yes' : 'No'),
onchange: () => {
settings.disableData = !settings.disableData;
save();
},
},
});
})

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.24",
"version":"0.25",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -1,3 +1,4 @@
0.01: New App.
0.02: Performance improvements.
0.03: Update clock_info to avoid a redraw
0.04: Fix clkinfo -- use .get instead of .show

View File

@ -76,21 +76,6 @@ var H = g.getHeight();
var menu = clock_info.load();
menu = menu.concat(dateMenu);
// Set draw functions for each item
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
item.hide();
var info = item.get();
drawText(item.name, info.text, (y%4)+1);
}
item.on('redraw', drawItem);
})
});
// 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;
@ -184,8 +169,9 @@ function drawMenuItems(menuItem) {
if (i >= menuItem.items.length) {
continue;
}
lock_input++;
menuItem.items[i].show();
var item = menuItem.items[i];
drawText(item.name, item.get().text, (i%4)+1);
}
}
@ -217,7 +203,6 @@ function drawText(key, value, line){
value = String(value).replace("\n", " ");
g.drawString(key + value, x, y);
lock_input -= 1;
}
@ -289,14 +274,8 @@ Bangle.on('charging',function(charging) {
draw();
});
var lock_input = 0;
Bangle.on('touch', function(btn, e){
if(lock_input > 0){
return;
}
lock_input = 0;
var left = parseInt(g.getWidth() * 0.22);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.22) + 20;

View File

@ -1,7 +1,7 @@
{
"id": "linuxclock",
"name": "Linux Clock",
"version": "0.03",
"version": "0.04",
"description": "A Linux inspired clock.",
"readme": "README.md",
"icon": "app.png",

View File

@ -0,0 +1 @@
0.01: New app!

BIN
apps/messagesdebug/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,3 @@
Bangle.on("message", (t, m) => {
require("Storage").open("messagesdebug.log", "a").write(`${t}: ${JSON.stringify(m)}\n`);
});

View File

@ -0,0 +1,71 @@
<html lang="en">
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<title>Messages Debug</title>
</head>
<body>
<div id="data"></div>
<button class="btn btn-default" id="btnSave">Save</button>
<button class="btn btn-default" id="btnDelete">Clear</button>
<button class="btn btn-default" id="btnReload" style="float:right">Reload</button>
<script src="../../core/lib/interface.js"></script>
<script>
const dataElement = document.getElementById("data");
let messages = "";
function getData() {
// show loading window
Util.showModal("Loading...");
// get the data
dataElement.innerHTML = "";
Util.readStorageFile(`messagesdebug.log`, data => {
messages = data.trim();
// remove window
Util.hideModal();
let disable = false;
if (data.length) {
dataElement.innerHTML = `<pre style="overflow:auto;border:1px solid black;">${data}</pre>`;
} else {
dataElement.innerHTML = "<b>No messages found</b>";
disable = true;
}
['btnSave','btnDelete','btnCopy'].forEach(id=>{
document.getElementById(id).disabled = disable;
});
});
}
const button = (id, fn) => document.getElementById(id).addEventListener("click", fn);
// Save messages to file
button("btnSave", function() {
let a = document.createElement("a");
let url = URL.createObjectURL(new Blob([messages], {type: "text/plain"}));
a.href = url;
a.download = "messagesdebug.log";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
});
// Delete the file from the watch
button("btnDelete", function() {
Util.showModal("Deleting...");
Util.eraseStorageFile("messagesdebug.log", function() {
Util.hideModal();
getData();
});
});
// Reload, in case we're e.g. using the IDE to send test messages
button("btnReload", getData);
// Called when app starts
function onInit() {
getData();
}
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"id": "messagesdebug",
"name": "Messages Debug",
"version": "0.01",
"description": "Write all messages to a file, for debugging purposes",
"icon": "app.png",
"type": "bootloader",
"tags": "tool,system",
"supports": ["BANGLEJS","BANGLEJS2"],
"interface": "interface.html",
"storage": [
{"name":"messagesdebug.boot.js","url":"boot.js"}
],
"data": [{"name":"messagesdebug.log"}]
}

View File

@ -1 +1,2 @@
0.01: First release
0.02: Use locale time

View File

@ -2,7 +2,7 @@
"id":"mosaic",
"name":"Mosaic Clock",
"shortName": "Mosaic Clock",
"version": "0.01",
"version": "0.02",
"description": "A fabulously colourful clock",
"readme": "README.md",
"icon":"mosaic.png",

View File

@ -58,13 +58,15 @@ function draw() {
);
}
}
let t = new Date();
let t = require("locale").time(new Date(), 1);
let hour = parseInt(t.split(":")[0]);
let minute = parseInt(t.split(":")[1]);
g.setBgColor(theme.fg);
g.setColor(theme.bg);
g.drawImage(digits[Math.floor(t.getHours()/10)], (mid_x-5)*s+o_w, (mid_y-7)*s+o_h, {scale:s});
g.drawImage(digits[t.getHours() % 10], (mid_x+1)*s+o_w, (mid_y-7)*s+o_h, {scale:s});
g.drawImage(digits[Math.floor(t.getMinutes()/10)], (mid_x-5)*s+o_w, (mid_y+1)*s+o_h, {scale:s});
g.drawImage(digits[t.getMinutes() % 10], (mid_x+1)*s+o_w, (mid_y+1)*s+o_h, {scale:s});
g.drawImage(digits[Math.floor(hour/10)], (mid_x-5)*s+o_w, (mid_y-7)*s+o_h, {scale:s});
g.drawImage(digits[hour % 10], (mid_x+1)*s+o_w, (mid_y-7)*s+o_h, {scale:s});
g.drawImage(digits[Math.floor(minute/10)], (mid_x-5)*s+o_w, (mid_y+1)*s+o_h, {scale:s});
g.drawImage(digits[minute % 10], (mid_x+1)*s+o_w, (mid_y+1)*s+o_h, {scale:s});
queueDraw(timeout);
}

View File

@ -10,7 +10,6 @@
"BANGLEJS",
"BANGLEJS2"
],
"allow_emulator": true,
"storage": [
{
"name": "pomoplus.app.js",
@ -34,4 +33,4 @@
"url": "settings.js"
}
]
}
}

View File

@ -60,3 +60,5 @@
0.53: Ensure that when clock is set, clockHasWidgets is set correctly too
0.54: If setting.json is corrupt, ensure it gets re-written
0.55: More strings tagged for automatic translation.
0.56: make System menu items shorter and more consistant, Eg 'Clock', intead
of 'Select Clock'

View File

@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.55",
"version": "0.56",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",

View File

@ -89,8 +89,8 @@ function showSystemMenu() {
/*LANG*/'Theme': ()=>showThemeMenu(),
/*LANG*/'LCD': ()=>showLCDMenu(),
/*LANG*/'Locale': ()=>showLocaleMenu(),
/*LANG*/'Select Clock': ()=>showClockMenu(),
/*LANG*/'Select Launcher': ()=>showLauncherMenu(),
/*LANG*/'Clock': ()=>showClockMenu(),
/*LANG*/'Launcher': ()=>showLauncherMenu(),
/*LANG*/'Date & Time': ()=>showSetTimeMenu()
};

View File

@ -0,0 +1,2 @@
0.01: first release
0.02: removed fast load, minimalism is useful for narrowing down on issues

View File

@ -1,6 +1,6 @@
# Simplest++ Clock
The simplest working clock, with fast load and clock_info
The simplest working clock, with clock_info
![](screenshot1.png)
![](screenshot2.png)
@ -36,8 +36,6 @@ This provides a working demo of how to use the clock_info modules.
## References
* [What is Fast Load and how does it work](http://www.espruino.com/Bangle.js+Fast+Load)
* [Clock Info Tutorial](http://www.espruino.com/Bangle.js+Clock+Info)
* [How to load modules through the IDE](https://github.com/espruino/BangleApps/blob/master/modules/README.md)

View File

@ -1,108 +1,95 @@
// Simplestpp Clock, see comments 'clock_info_support'
function draw() {
var date = new Date();
var timeStr = require("locale").time(date,1);
var h = g.getHeight();
var w = g.getWidth();
g.reset();
g.setColor(g.theme.bg);
g.fillRect(Bangle.appRect);
g.setFont('Vector', w/3);
g.setFontAlign(0, 0);
g.setColor(g.theme.fg);
g.drawString(timeStr, w/2, h/2);
clockInfoMenu.redraw(); // clock_info_support
queueDraw(); // queue draw in one minute
}
// timeout used to update every minute
var drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
/**
*
* Simplestpp Clock
*
* The entire clock code is contained within the block below this
* supports 'fast load'
*
* To add support for clock_info_supprt we add the code marked at [1] and [2]
* clock_info_support
* this is the callback function that get invoked by clockInfoMenu.redraw();
*
* We will display the image and text on the same line and centre the combined
* length of the image+text
*
*/
function clockInfoDraw(itm, info, options) {
{
// 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
g.reset().setFont('Vector',24).setBgColor(options.bg).setColor(options.fg);
let draw = function() {
var date = new Date();
var timeStr = require("locale").time(date,1);
var h = g.getHeight();
var w = g.getWidth();
g.reset();
g.setColor(g.theme.bg);
g.fillRect(Bangle.appRect);
//use info.text.toString(), steps does not have length defined
var text_w = g.stringWidth(info.text.toString());
// gap between image and text
var gap = 10;
// width of the image and text combined
var w = gap + (info.img ? 24 :0) + text_w;
// different fg color if we tapped on the menu
if (options.focus) g.setColor(options.hl);
g.setFont('Vector', w/3);
g.setFontAlign(0, 0);
g.setColor(g.theme.fg);
g.drawString(timeStr, w/2, h/2);
clockInfoMenu.redraw(); // clock_info_support
// schedule a draw for the next minute
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
/**
* clock_info_support
* this is the callback function that get invoked by clockInfoMenu.redraw();
*
* We will display the image and text on the same line and centre the combined
* length of the image+text
*
*
*/
let clockInfoDraw = (itm, info, options) => {
g.reset().setFont('Vector',24).setBgColor(options.bg).setColor(options.fg);
//use info.text.toString(), steps does not have length defined
var text_w = g.stringWidth(info.text.toString());
// gap between image and text
var gap = 10;
// width of the image and text combined
var w = gap + (info.img ? 24 :0) + text_w;
// different fg color if we tapped on the menu
if (options.focus) g.setColor(options.hl);
// clear the whole info line
g.clearRect(0, options.y -1, g.getWidth(), options.y+24);
// draw the image if we have one
if (info.img) {
// image start
var x = (g.getWidth() / 2) - (w/2);
g.drawImage(info.img, x, options.y);
// draw the text to the side of the image (left/centre alignment)
g.setFontAlign(-1,0).drawString(info.text, x + 23 + gap, options.y+12);
} else {
// text only option, not tested yet
g.setFontAlign(0,0).drawString(info.text, g.getWidth() / 2, options.y+12);
}
};
// clock_info_support
// retrieve all the clock_info modules that are installed
let clockInfoItems = require("clock_info").load();
// clock_info_support
// setup the way we wish to interact with the menu
// the hl property defines the color the of the info when the menu is selected after tapping on it
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:64, y:132, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#0ff"} );
// clear the whole info line
g.clearRect(0, options.y -1, g.getWidth(), options.y+24);
// timeout used to update every minute
var drawTimeout;
g.clear();
// draw the image if we have one
if (info.img) {
// image start
var x = (g.getWidth() / 2) - (w/2);
g.drawImage(info.img, x, options.y);
// draw the text to the side of the image (left/centre alignment)
g.setFontAlign(-1,0).drawString(info.text, x + 23 + gap, options.y+12);
} else {
// text only option, not tested yet
g.setFontAlign(0,0).drawString(info.text, g.getWidth() / 2, options.y+12);
}
// Show launcher when middle button pressed, add updown button handlers
Bangle.setUI({
mode : "clock",
remove : function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
// remove info menu
clockInfoMenu.remove();
delete clockInfoMenu;
}
});
}
// Load widgets
Bangle.loadWidgets();
draw();
setTimeout(Bangle.drawWidgets,0);
} // end of clock
/**
* clock_info_support: retrieve all the clock_info modules that are
* installed
*
*/
let clockInfoItems = require("clock_info").load();
/**
* clock_info_support: setup the way we wish to interact with the menu
* the hl property defines the color the of the info when the menu is
* selected after tapping on it
*
*/
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:64, y:132, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#0ff"} );
// Clear the screen once, at startup
g.clear();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();

View File

@ -2,8 +2,8 @@
"id": "simplestpp",
"name": "Simplest++ Clock",
"shortName": "Simplest++",
"version": "0.01",
"description": "The simplest working clock, with fast load and clock_info, acts as a tutorial piece",
"version": "0.02",
"description": "The simplest working clock, with clock_info, acts as a tutorial piece",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot3.png"}],

View File

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

16
apps/spaceclock/README.md Normal file
View File

@ -0,0 +1,16 @@
# Space Clock (Casio)
![Light Theme version](spaceclock_light_big.png)
## Description
This watch face is inspired by the Casio Prototype, which was made by Khryzteen Nakamura from Clockology Fans on Facebook.
## Features
- Time and Date
- Weekday
- Temperature
- HeartRate
- Battery
- Step Count
- Support of light and dark theme
## Tips
Click on the heart icon to deactivate the heart rate monitor.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEewgbYhGIxGAC6wABBAcM5gACC5wYCCwgYLIwYcBhoWFDBQTBAofcC4/AGBIEDCw4wLJARdGAAfQGBYWJJBQwCC5SSLC6wwBC6owBwhgUDASQKC5UMpGIpgXU5koMRIXM4hiJC5gNBMRAXN5hiBDA0IC5oPBPYz+CABAQElAYFWgIYJC4h7BDAZeBHAJINPYQYC6DlCGBENRQwRBhgNCGBSjG4QxBBoYwQGIIrEPJS8GIgYADYZwIDDAgXJABQXXAAY"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

237
apps/spaceclock/app.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
{ "id": "spaceclock",
"name": "Space Clock (Casio Style)",
"shortName":"Space Clock",
"version":"0.01",
"description": "Watch face in the style of Casio Prototype Space Resist",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock, casio, retro",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"spaceclock.app.js","url":"app.js"},
{"name":"spaceclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

1
apps/tictactoe/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Stable Launch

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwIJGv//AAX+oEAwEBgECApuD4IFBocww1hAoXwxkxAoNB8GIiIFC4AFDofgCIYdFoEQFIZBRLLoFBHYkAI4YFBKYYFHCIodFLO8M4RHDh4FCKYMHwQFELIkPBYQdFFIVCLK8AAAg="))

210
apps/tictactoe/app.js Normal file
View File

@ -0,0 +1,210 @@
//////////////////////////////
// Tic - Tac - Toe
// Stable Version 1.0 - 12/31/2022
// MissionMake
//////////////////////////////
////////////////////////////
// TODO:
// Implement Computer Player
// Beginning Screen (pick player to go first, pick one or two player)
////////////////////////////
//create 3x3 array to log plays Xs defined as 1, Os defined as -1, blank is undefined, array is initialized undefined, player is which players turn is active (using 1,-1 definition to match matrix), active is if a game is being played
var arr1 = new Array(3);
var arr2 = new Array(3);
var arr3 = new Array(3);
var arr = new Array(arr1,arr2,arr3);
var val = 0;
var player;
var active = false;
var select = false;
var next = 0;
var winval =0;
var ex = require("heatshrink").decompress(atob("mEwwI63jACEngCEvwCEv4CB/wCBn+AgP8AoMf4ED/AFBh/gg/wAoIDBA4IFBB4ITBAoIbBD4I8C/wrCGAQuCGAQuCGAQuCGAQuCAo4RFDoopFGohBFJopZFMopxFPoqJFSoqhFVooA0A"));
var oh = require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCB/wCBBAU/AQIUCj8AgIzCh+AgYmCg/AgYyCAYIHBAoXgg+AAoMBApkPLgZKBAtBBRLIprDMoJxFPoqJFSoyhCAQStFXIrFFaIrdFdIwAVA"));
//calculates sum of rows, colums, and diagonals for a win condition. passes winner to win() and breaks out of calcs
function calcWin(){
winval = 0;
//sum of row
for(let i = 0; i<3; i++){
val=0;
for(let j = 0; j<3; j++){
val = arr[i][j]+val;
}
if (Math.abs(val)==3) {
winval = val;
}
}
//sum of columns
for(let j = 0; j<3; j++){
val=0;
for (let i = 0; i<3; i++){
val = arr[i][j]+val;
}
//if win set winval to val
if (Math.abs(val)==3) {
winval = val;
}
}
//Sum of ul to lr
val=0;
val = arr[0][0]+arr[1][1]+arr[2][2];
//if win set winval to val
if (Math.abs(val)==3) {
winval = val;
}
//sum of ur to ll
val=0;
val = arr[0][2]+arr[1][1]+arr[2][0];
//if win set winval to val
if (Math.abs(val)==3){
winval = val;
}
//draw check
// drawChk is sum absolute value of array, if drawChk = 9 then there is a draw
drawChk = 0;
for(let i = 0; i<3; i++){
for(let j = 0; j<3; j++){
drawChk = drawChk + Math.abs(arr[i][j]);
}
}
//checks for win cases and posts correct message, otherwise play
if (winval == 3){
active = false;
E.showAlert("Player X Wins").then(start);
} else if (winval == -3){
active = false;
E.showAlert("Player O Wins").then(start);
} else if (drawChk == 9) {
active = false;
E.showAlert("Draw").then(start);
}else{
//If no win then play
draw();
}
}
function draw(){
g.clear();
if (player ==1){
playerIcon = "X";
} else if(player == -1){
playerIcon = "O";
}
//Banner Displays player turn
E.showMessage("","Player "+ playerIcon);
//drawboard
g.drawLine(62,24,62,176);
g.drawLine(112,24,112,176);
g.drawLine(12,74,164,74);
g.drawLine(12,124,164,124);
//loop through array and draw markers
for(let i = 0; i<3; i++){
for(let j = 0; j<3; j++){
if(arr[j][i] == -1){
g.drawImage(oh,i*50+12,j*50+24);//, {scale:1.05});
} else if (arr[j][i] == 1){
g.drawImage(ex,i*50+12,j*50+24);//, {scale:1.05});
} else {
//blank spot
}
}
}
select=false;
wait();
}
// Square locations
//12,24;62,24,112,24
//12,74;62,74,112,74
//12,124;62,124,112,124
function placeMarker(){
///Determine marker square
if (x <= 62) {
b = 0;
} else if (x <= 112){
b = 1;
} else {
b = 2;
}
if (y <= 74) {
a = 0;
} else if (y <= 124){
a = 1;
} else {
a = 2;
}
//if empty
if( arr[a][b] == undefined){
//record in array
arr[a][b] = player;
player=player*-1;
select = false;
calcWin();
} else{ //if filled
// This could just do nothing
E.showAlert("SpaceFilled Try again").then(draw);
}
}
// Wait loop which is run until a tap is selected
function wait(){
//Terminal.println("wait");
if(select == true){
placeMarker();
} else {
setTimeout(wait,300);
}
}
// Starts new game
// Draws the start pattern, sets first player to x and goes to play
function start(){
//reset array to undefined
arr1.fill(undefined);
arr2.fill(undefined);
arr3.fill(undefined);
g.clear();
active =true;
player=1;
draw();
}
//Looks for touch
Bangle.on('touch', function(zone,e) {
x = Object.values(e)[0];
y = Object.values(e)[1];
//if game is active
if(active == true){
g.fillCircle(x, y, 10);
select = true;
}
if(active == false){
start();
}
});
start();

BIN
apps/tictactoe/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

View File

@ -0,0 +1,17 @@
{ "id": "tictactoe",
"name": "TicTacToe",
"shortName":"TicTacToe",
"icon": "app.png",
"version":"0.01",
"description": "Tic Tac Toe for two players!",
"tags": "game",
"storage": [
{"name":"tictactoe.app.js","url":"app.js"},
{"name":"tictactoe.img","url":"app-icon.js","evaluate":true}
],
"screenshots" : [
{ "url":"tttscreenshot.png" },
{ "url":"tttscreenshot2.png" }
],
"supports": ["BANGLEJS2"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -136,7 +136,8 @@
<button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button>
<button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button>
<button class="btn" id="installdefault">Install default apps</button>
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button></p>
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button>
<button class="btn" id="newGithubIssue" data-tooltip="Create a new issue on github">New issue on github</button></p>
<p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
<button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
<h3>Settings</h3>

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