Merge branch 'master' of github.com:wagnerf42/BangleApps
|
@ -278,6 +278,7 @@ and which gives information about the app for the Launcher.
|
|||
// 'bluetooth' - uses Bluetooth LE
|
||||
// 'system' - used by the system
|
||||
// 'clkinfo' - provides or uses clock_info module for data on your clock face or clocks that support it (see apps/clock_info/README.md)
|
||||
// 'health' - e.g. heart rate monitors or step counting
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
<label class="chip tooltip" filterid="outdoors" data-tooltip="For outdoor use">Outdoors</label>
|
||||
<label class="chip tooltip" filterid="ram" data-tooltip="Apps that don't save anything to flash memory">Online</label>
|
||||
<label class="chip tooltip" filterid="clkinfo" data-tooltip="Info displayed on clocks, or clocks with info">Clock Info</label>
|
||||
<label class="chip tooltip" filterid="health" data-tooltip="Apps for your health">Health</label>
|
||||
<label class="chip tooltip" filterid="favourites" data-tooltip="Apps that you've liked ❤️">Favourites</label>
|
||||
</div>
|
||||
<div class="sort-nav hidden">
|
||||
|
@ -176,9 +177,13 @@
|
|||
</label>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-alwaysAllowUpdate">
|
||||
<i class="form-icon"></i> Always allow to reinstall apps in place regardless of the version
|
||||
<i class="form-icon"></i> Always show "reinstall app" button <i class="icon icon-refresh"></i> regardless of the version
|
||||
</label>
|
||||
<button class="btn" id="defaultsettings">Reset to default App Loader settings</button>
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="settings-autoReload">
|
||||
<i class="form-icon"></i> Automatically reload watch after app App Loader actions (removes "Hold button" prompt)
|
||||
</label>
|
||||
<button class="btn" id="defaultsettings">Reset App Loader settings to defaults</button>
|
||||
</details>
|
||||
</div>
|
||||
<div id="more-deviceinfo" style="display:none">
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Change log created
|
|
@ -0,0 +1,18 @@
|
|||
Tyreid
|
||||
|
||||
Tyreid is a Bluetooth war-driving app for the Bangle.js 2.
|
||||
|
||||
Menu options:
|
||||
- Start: This turns on the Bluetooth and starts logging Bluetooth packets with time, latitude, and longitude information to a CSV file.
|
||||
- Pause/Continue: These functions pause the capture and then allow it to resume.
|
||||
- Devices: When paused this menu option will display the MAC addresses of discovered Bluetooth devices. Selecting a device will then display the MAC, Manufacturer code, the time it was first seen, and the RSSI of the first sighting.
|
||||
- Marker: This command adds a 'marker' to the CSV log, which consists of the time and location information, but the Bluetooth packet information is replaced with the word MARKER. Markers can also be added by pressing the watch's button.
|
||||
- Exit: This exits the app.
|
||||
|
||||
The current number of discovered devices is displayed in the top left corner.
|
||||
This value is displayed in green when the GPS has a fix, or red otherwise.
|
||||
|
||||
To retrieve the CSV file, connect to the watch through the Espruino web IDE (https://www.espruino.com/ide/). From there the files stored on the watch can be downloaded by clicking the storage icon in the IDE's central column.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("MDACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAtAAAAAAAAAAAAAAB/QAAAAAAAAAAAAAD/0AAAAAAAAAAAAAH+9AAAAAAAAAAAAAPtfQAAAAAAAAAAAAudH0AAAAAAAAAAAB8tB9AAAAAAAAAAAD0tAfQAAAAAAAAAAHgtAH0AAAAAAAAAAPAtAB9AAAAAAAAAAtAtAAfQAAAAAAAAB8AtAAH0AAAAAAAAD0AtAAB9AAAAAAAALgAtAAAfQAAAAAAAfAAtAAAH0AAAAAAA9AAtAAAB9AAAAAAC4AAtAAAAfQAAAAADwAAtAAAALwAAAAAAQAAtAAAAvgAAAAAAAAAsAAAC9AAAAAAAAAAsAAAP0AAAAAAAAAAsAAB/AAAAAAAAAAAsAAH4AAAAAAAAAAAsAAvQAAAAAAAAAAAsAD9AAAAAAAAAAAAsAD0AAAAAAAAAAAAsAB8AAAAAAAAAAAAsAAtAAAAAAAAAAAAsAAPQAAAAAAAAAAAsAAHwAAAAAAAAAAAsAAC4AAAAAAAAAAAsAAA9AAAAAAAAAAAsAAAPAAAAAAAAAAAsAAAHgAAAAAAAAAA8AAAC0AAAAAAAAAA8AAAA8AAAAAAAAAA8AAAAEAAAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "Tyreid",
|
||||
"name": "Tyreid",
|
||||
"shortName":"Tyreid",
|
||||
"version":"0.01",
|
||||
"description": "Bluetooth war-driving app for Bangle.js 2",
|
||||
"icon": "small_logo.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"Tyreid.app.js","url":"app.js"},
|
||||
{"name":"Tyreid.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 8.2 KiB |
|
@ -45,4 +45,7 @@
|
|||
0.40: Use substring of message when it's longer than fits the designated menu entry.
|
||||
0.41: Fix a menu bug affecting alarms with empty messages.
|
||||
0.42: Fix date not getting saved in event edit menu when tapping Confirm
|
||||
0.43: New settings: Show confirm, Show Overflow, Show Type.
|
||||
0.43: New settings: Show confirm, Show Overflow, Show Group.
|
||||
0.44: Add "delete timer after expiration" setting to events.
|
||||
0.45: Fix new alarm when selectedAlarm is undefined
|
||||
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
|
||||
|
|
|
@ -73,39 +73,57 @@ function formatAlarmProperty(msg) {
|
|||
}
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
function showMainMenu(scroll, group) {
|
||||
const menu = {
|
||||
"": { "title": /*LANG*/"Alarms & Timers" },
|
||||
"< Back": () => load(),
|
||||
/*LANG*/"New...": () => showNewMenu()
|
||||
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
|
||||
"< Back": () => group ? showMainMenu() : load(),
|
||||
/*LANG*/"New...": () => showNewMenu(group)
|
||||
};
|
||||
const getGroups = settings.showGroup && !group;
|
||||
const groups = getGroups ? {} : undefined;
|
||||
var showAlarm;
|
||||
|
||||
alarms.forEach((e, index) => {
|
||||
showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
|
||||
if(showAlarm) {
|
||||
menu[trimLabel(getLabel(e),40)] = {
|
||||
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
|
||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller.scroll, group)
|
||||
};
|
||||
} else if (getGroups) {
|
||||
groups[e.group] = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
if (!group) {
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g));
|
||||
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showNewMenu() {
|
||||
E.showMenu({
|
||||
var scroller = E.showMenu(menu).scroller;
|
||||
}
|
||||
|
||||
function showNewMenu(group) {
|
||||
const newMenu = {
|
||||
"": { "title": /*LANG*/"New..." },
|
||||
"< Back": () => showMainMenu(),
|
||||
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
|
||||
"< Back": () => showMainMenu(group),
|
||||
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined, false, null, group),
|
||||
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined),
|
||||
/*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true)
|
||||
});
|
||||
/*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true, null, group)
|
||||
};
|
||||
|
||||
if (group) delete newMenu[/*LANG*/"Timer"];
|
||||
E.showMenu(newMenu);
|
||||
}
|
||||
|
||||
function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
||||
function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||
var isNew = alarmIndex === undefined;
|
||||
|
||||
var alarm = require("sched").newDefaultAlarm();
|
||||
if (isNew && group) alarm.group = group;
|
||||
if (withDate || (selectedAlarm && selectedAlarm.date)) {
|
||||
alarm.del = require("sched").getSettings().defaultDeleteExpiredTimers;
|
||||
}
|
||||
alarm.dow = handleFirstDayOfWeek(alarm.dow);
|
||||
|
||||
if (selectedAlarm) {
|
||||
|
@ -124,7 +142,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
"< Back": () => {
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
showMainMenu(scroll, group);
|
||||
},
|
||||
/*LANG*/"Hour": {
|
||||
value: time.h,
|
||||
|
@ -168,7 +186,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
keyboard.input({text:alarm.msg}).then(result => {
|
||||
alarm.msg = result;
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
@ -181,7 +199,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
keyboard.input({text:alarm.group}).then(result => {
|
||||
alarm.group = result;
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
@ -193,10 +211,13 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
/*LANG*/"Repeat": {
|
||||
value: decodeRepeat(alarm),
|
||||
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, date || alarm.dow, (repeat, dow) => {
|
||||
if (repeat) {
|
||||
alarm.del = false; // do not auto delete a repeated alarm
|
||||
}
|
||||
alarm.rp = repeat;
|
||||
alarm.dow = dow;
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||
})
|
||||
},
|
||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
|
||||
|
@ -204,15 +225,19 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
value: alarm.as,
|
||||
onchange: v => alarm.as = v
|
||||
},
|
||||
/*LANG*/"Delete After Expiration": {
|
||||
value: alarm.del,
|
||||
onchange: v => alarm.del = v
|
||||
},
|
||||
/*LANG*/"Hidden": {
|
||||
value: alarm.hidden || false,
|
||||
onchange: v => alarm.hidden = v
|
||||
},
|
||||
/*LANG*/"Cancel": () => showMainMenu(),
|
||||
/*LANG*/"Cancel": () => showMainMenu(scroll, group),
|
||||
/*LANG*/"Confirm": () => {
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
showMainMenu(scroll, group);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -225,6 +250,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
delete menu[/*LANG*/"Day"];
|
||||
delete menu[/*LANG*/"Month"];
|
||||
delete menu[/*LANG*/"Year"];
|
||||
delete menu[/*LANG*/"Delete After Expiration"];
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
|
@ -233,10 +259,10 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
|
|||
if (confirm) {
|
||||
alarms.splice(alarmIndex, 1);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
showMainMenu(scroll, group);
|
||||
} else {
|
||||
alarm.t = require("time_utils").encodeTime(time);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -283,7 +309,6 @@ function decodeRepeat(alarm) {
|
|||
}
|
||||
|
||||
function showEditRepeatMenu(repeat, day, dowChangeCallback) {
|
||||
var originalRepeat = repeat;
|
||||
var dow;
|
||||
|
||||
const menu = {
|
||||
|
@ -316,26 +341,32 @@ function showEditRepeatMenu(repeat, day, dowChangeCallback) {
|
|||
},
|
||||
/*LANG*/"Custom": {
|
||||
value: isCustom ? decodeRepeat({ rp: true, dow: dow }) : false,
|
||||
onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, originalRepeat, originalDow)
|
||||
onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, repeat, originalDow)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// var date = day; // eventually: detect day of date and configure a repeat e.g. 3rd Monday of Month
|
||||
dow = EVERY_DAY;
|
||||
repeat = repeat || {interval: "month", num: 1};
|
||||
const repeatObj = repeat || {interval: "month", num: 1};
|
||||
|
||||
restOfMenu = {
|
||||
/*LANG*/"Every": {
|
||||
value: repeat.num,
|
||||
value: repeatObj.num,
|
||||
min: 1,
|
||||
onchange: v => repeat.num = v
|
||||
onchange: v => {
|
||||
repeat = repeatObj;
|
||||
repeat.num = v;
|
||||
}
|
||||
},
|
||||
/*LANG*/"Interval": {
|
||||
value: INTERVALS.indexOf(repeat.interval),
|
||||
value: INTERVALS.indexOf(repeatObj.interval),
|
||||
format: v => INTERVAL_LABELS[v],
|
||||
min: 0,
|
||||
max: INTERVALS.length - 1,
|
||||
onchange: v => repeat.interval = INTERVALS[v]
|
||||
onchange: v => {
|
||||
repeat = repeatObj;
|
||||
repeat.interval = INTERVALS[v];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.43",
|
||||
"version": "0.46",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Actually upload correct code
|
||||
0.03: Display sea-level pressure, too, and allow calibration
|
||||
0.04: Switch to using system code for pressure calibration
|
||||
|
|
|
@ -3,59 +3,54 @@ Bangle.setBarometerPower(true, "altimeter");
|
|||
g.clear(1);
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
var zero = 0;
|
||||
var R = Bangle.appRect;
|
||||
var y = R.y + R.h/2;
|
||||
var MEDIANLENGTH = 20;
|
||||
var avr = [], median;
|
||||
var value = 0;
|
||||
var avr = [];
|
||||
|
||||
function getStandardPressure(altitude) {
|
||||
const P0 = 1013.25; // standard pressure at sea level in hPa
|
||||
const T0 = 288.15; // standard temperature at sea level in K
|
||||
const g0 = 9.80665; // standard gravitational acceleration in m/s^2
|
||||
const R = 8.31432; // gas constant in J/(mol*K)
|
||||
const M = 0.0289644; // molar mass of air in kg/mol
|
||||
const L = -0.0065; // temperature lapse rate in K/m
|
||||
|
||||
const temperature = T0 + L * altitude; // temperature at the given altitude
|
||||
const pressure = P0 * Math.pow((temperature / T0), (-g0 * M) / (R * L)); // pressure at the given altitude
|
||||
|
||||
return pressure;
|
||||
}
|
||||
|
||||
function convertToSeaLevelPressure(pressure, altitude) {
|
||||
return 1013.25 * (pressure / getStandardPressure(altitude));
|
||||
function fmt(t) {
|
||||
if ((t > -100) && (t < 1000))
|
||||
t = t.toFixed(1);
|
||||
else
|
||||
t = t.toFixed(0);
|
||||
return t;
|
||||
}
|
||||
|
||||
Bangle.on('pressure', function(e) {
|
||||
while (avr.length>MEDIANLENGTH) avr.pop();
|
||||
avr.unshift(e.altitude);
|
||||
median = avr.slice().sort();
|
||||
let median = avr.slice().sort();
|
||||
g.reset().clearRect(0,y-30,g.getWidth()-10,R.h);
|
||||
if (median.length>10) {
|
||||
var mid = median.length>>1;
|
||||
value = E.sum(median.slice(mid-4,mid+5)) / 9;
|
||||
t = value-zero;
|
||||
if ((t > -100) && (t < 1000))
|
||||
t = t.toFixed(1);
|
||||
else
|
||||
t = t.toFixed(0);
|
||||
var value = E.sum(median.slice(mid-4,mid+5)) / 9;
|
||||
} else {
|
||||
var value = median[median.length>>1];
|
||||
}
|
||||
t = fmt(value);
|
||||
|
||||
g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y);
|
||||
sea = convertToSeaLevelPressure(e.pressure, value-zero);
|
||||
|
||||
let o = Bangle.getOptions();
|
||||
let sea = o.seaLevelPressure;
|
||||
t = sea.toFixed(1) + " " + e.temperature.toFixed(1);
|
||||
if (0) {
|
||||
print("alt raw:", value.toFixed(1));
|
||||
print("temperature:", e.temperature);
|
||||
print("pressure:", e.pressure);
|
||||
print("sea pressure:", sea);
|
||||
print("std pressure:", getStandardPressure(value-zero));
|
||||
}
|
||||
g.setFont("Vector",25).setFontAlign(-1,0).drawString(t,
|
||||
10, R.y+R.h - 35);
|
||||
}
|
||||
g.setFont("Vector",25).setFontAlign(-1,0).drawString(t, 10, R.y+R.h - 35);
|
||||
});
|
||||
|
||||
function setPressure(m, a) {
|
||||
o = Bangle.getOptions();
|
||||
print(o);
|
||||
o.seaLevelPressure = o.seaLevelPressure * m + a;
|
||||
Bangle.setOptions(o);
|
||||
avr = [];
|
||||
}
|
||||
|
||||
print(g.getFonts());
|
||||
g.reset();
|
||||
g.setFont("Vector:15");
|
||||
|
@ -63,9 +58,9 @@ g.setFontAlign(0,0);
|
|||
g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
|
||||
g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62);
|
||||
g.flip();
|
||||
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2);
|
||||
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"STD", g.getWidth()-5, g.getHeight()/2);
|
||||
Bangle.setUI("updown", btn=> {
|
||||
if (!btn) zero=value;
|
||||
if (btn<0) zero-=5;
|
||||
if (btn>0) zero+=5;
|
||||
if (!btn) setPressure(0, 1013.25);
|
||||
if (btn<0) setPressure(1, 1);
|
||||
if (btn>0) setPressure(1, -1);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "altimeter",
|
||||
"name": "Altimeter",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.1: Initial release
|
||||
0.2: Added more descriptive approximations
|
||||
0.2f: Bug fixes: Incorrect hour drawn after 50 mins, incorrect quarter minute drawn after 50 mins
|
||||
0.3: Added touch interaction to display exact time and date.
|
|
@ -0,0 +1 @@
|
|||
atob("MDAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArgVYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABW19cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsrNcrAACBVoGsVgAAgVaBrFYAAKzXK4GsKwAAgayBAAAAgVYAVoEAAAAAAAAAAADXVqyBAACs14Gs1ysArNeBrNcrAIHX14HXgQCB14HXrAAAVtdW11YAAAAAAAAAAFbXK4GsAACs1wAr11YArNcAK9dWAADXrABWVgDXgQCB1wAAAKzXgQAAAAAAAAAAAKzXrNfXKwCs1wAA14EArKwAK9dWAADXVgAAAADXgQBW1ysAAIHXVgAAAAAAAAAAANfXgYHXVgCs11aB11YArNcrgddWACvXgSsAAACsrCus1wAAK9es1ysAAAAAAAAAK9dWAACsrACsrKzXrAAArKzX16wAVtfX16wAAAAr19fXKwAArKwArKwAAAAAAAAAAAAAAAAAAACsrAArAAAArIEAKwAAAAAAAAAAAAAAACsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsrAAAAAAArIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWVgAAAAAAVlYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArVoErAAAAAAAAAAAAAAAAAAAAAAArVgAAAAAAAAAAAAAAAAAAAAAAAAArrNfXgQCBrNdWAAAAAAAAAAAAAAAAAAAAAABW1wAAAAAAAAAAAAAAAAAAAAAAACvXrCtWgQAAANdWAAAAAAArKwAAAAAAACsAAABW1wAAAAAAAAAAAAAAAAAAAAAAAFbXKwAAAAAAANdWAAAAAIHX16wAAACB19fXVgBW1wAA14EAAAAAAAAAAAAAAAAAAIGsAAAAAAAAANdWAAAAK9eBVteBACvXgSuBVgBW1wBW1ysAAAAAAAAAAAAAAAAAAIHXAAAAAAAAANdWAAAAVtcrAKyBAFbXKwAAAABW16zX1wAAAAAAAAAAAAAAAAAAAFbXVgAAAAAAANeBAAAAVtcrANeBAFbXKwAAAABW14HXrAAAAAAAAAAAAAAAAAAAAACs14GBrAAAAKzXrIEAK9esrNdWACvX14GBVgBW1wAr11YAAAAAAAAAAAAAAAAAAAAAVoGBgQAAACusrIEAACusrFYAAAArgaysVgBWgQAAgYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
|
@ -0,0 +1,156 @@
|
|||
//load fonts
|
||||
require("FontSinclair").add(Graphics);
|
||||
require("FontTeletext5x9Ascii").add(Graphics);
|
||||
|
||||
//const
|
||||
|
||||
const numbers = {
|
||||
"0": "Twelve",
|
||||
"1": "One",
|
||||
"2": "Two",
|
||||
"3": "Three",
|
||||
"4": "Four",
|
||||
"5": "Five",
|
||||
"6": "Six",
|
||||
"7": "Seven",
|
||||
"8": "Eight",
|
||||
"9": "Nine",
|
||||
"10": "Ten",
|
||||
"11": "Eleven",
|
||||
"12": "Twelve",
|
||||
"13": "One",
|
||||
"14": "Two",
|
||||
"15": "Three",
|
||||
"16": "Four",
|
||||
"17": "Five",
|
||||
"18": "Six",
|
||||
"19": "Seven",
|
||||
"20": "Eight",
|
||||
"21": "Nine",
|
||||
"22": "Ten",
|
||||
"23": "Eleven",
|
||||
"24": "Twelve",
|
||||
};
|
||||
|
||||
const minutesByQuarterString = {
|
||||
0: "O'Clock",
|
||||
15: "Fifteen",
|
||||
30: "Thirty",
|
||||
45: "Fourty-Five"
|
||||
};
|
||||
|
||||
const width = g.getWidth();
|
||||
const height = g.getHeight();
|
||||
let drawTimeout;
|
||||
|
||||
const getNearestHour = (hours, minutes) => {
|
||||
if (minutes > 54) {
|
||||
return hours + 1;
|
||||
}
|
||||
return hours;
|
||||
};
|
||||
|
||||
const getApproximatePrefix = (minutes, minutesByQuarter) => {
|
||||
if (minutes === minutesByQuarter) {
|
||||
return " exactly";
|
||||
} else if (minutesByQuarter - minutes < -54) {
|
||||
return " nearly";
|
||||
} else if (minutesByQuarter - minutes < -5) {
|
||||
return " after";
|
||||
} else if (minutesByQuarter - minutes < 0) {
|
||||
return " just after";
|
||||
} else if (minutesByQuarter - minutes > 5) {
|
||||
return " before";
|
||||
} else {
|
||||
return " nearly";
|
||||
}
|
||||
};
|
||||
|
||||
const getMinutesByQuarter = minutes => {
|
||||
if (minutes < 10) {
|
||||
return 0;
|
||||
} else if (minutes < 20) {
|
||||
return 15;
|
||||
} else if (minutes < 40) {
|
||||
return 30;
|
||||
} else if (minutes < 55) {
|
||||
return 45;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function () {
|
||||
drawTimeout = undefined;
|
||||
drawTime();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
const drawTimeExact = () => {
|
||||
var dateTime = Date();
|
||||
var hours = dateTime.getHours();
|
||||
var minutes = dateTime.getMinutes().toString().padStart(2,0);
|
||||
var day = dateTime.getDay();
|
||||
var date = dateTime.getDate();
|
||||
var month = dateTime.getMonth();
|
||||
var year = dateTime.getFullYear();
|
||||
g.clear();
|
||||
g.setBgColor(0,0,0);
|
||||
g.clearRect(0,0,width, height);
|
||||
g.setColor(1,1,1);
|
||||
g.setFont("Vector", 30);
|
||||
g.drawString(hours + ":" + minutes, (width - g.stringWidth(hours + ":" + minutes))/2, height * 0.3, false);
|
||||
g.setFont("Vector", 26);
|
||||
g.drawString(month + 1 + "/" + date + "/" + year, (width - g.stringWidth(month + 1 + "/" + date + "/" + year))/2, height * 0.6, false);
|
||||
};
|
||||
|
||||
const drawTime = () => {
|
||||
//Grab time vars
|
||||
var date = Date();
|
||||
var hour = date.getHours();
|
||||
var minutes = date.getMinutes();
|
||||
var minutesByQuarter = getMinutesByQuarter(minutes);
|
||||
|
||||
//reset graphics
|
||||
g.clear();
|
||||
g.reset();
|
||||
|
||||
//Build watch face
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.clearRect(0, 0, width, height);
|
||||
g.setFont("Vector", 22);
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawString("It's" + getApproximatePrefix(minutes, minutesByQuarter), (width - g.stringWidth("It's" + getApproximatePrefix(minutes, minutesByQuarter))) / 2, height * 0.25, false);
|
||||
g.setFont("Vector", 30);
|
||||
g.drawString(numbers[getNearestHour(hour, minutes)], (width - g.stringWidth(numbers[getNearestHour(hour, minutes)])) / 2, height * 0.45, false);
|
||||
g.setFont("Vector", 22);
|
||||
g.drawString(minutesByQuarterString[minutesByQuarter], (width - g.stringWidth(minutesByQuarterString[minutesByQuarter])) / 2, height * 0.7, false);
|
||||
|
||||
queueDraw();
|
||||
};
|
||||
|
||||
g.clear();
|
||||
drawTime();
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (on) {
|
||||
drawTime();
|
||||
} else {
|
||||
if (idTimeout) {
|
||||
clearTimeout(idTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('touch', function(button, xy){
|
||||
drawTimeExact();
|
||||
setTimeout(drawTime, 7000);
|
||||
});
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,18 @@
|
|||
{ "id": "approxclock",
|
||||
"name": "Approximate Clock",
|
||||
"shortName" : "Approx Clock",
|
||||
"version": "0.3",
|
||||
"icon": "app.png",
|
||||
"description": "A really basic spelled out time display for people looking for the vague time at a glance.",
|
||||
"readme": "readme.md",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"approxclock.app.js","url":"app.js"},
|
||||
{"name":"approxclock.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"screenshots": [
|
||||
{"url": "screenshot.png"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
## Approximate Clock
|
||||
|
||||
### Description
|
||||
|
||||
Get a rough idea of the time at a quick glance, mostly made for myself based on a similar watchface on pebble. I find this keeps me from checking my watch too often and also saves me from moments of severe brainfart staring at these mysterious symbols we call numbers.
|
||||
|
||||
Exact time and date can be viewed temporarily by touching the screen.
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -3,3 +3,6 @@
|
|||
0.03: Update to use Bangle.setUI instead of setWatch
|
||||
0.04: Tell clock widgets to hide.
|
||||
0.05: Added adjustment for Bangle.js magnetometer heading fix
|
||||
0.06: optimized to update much faster
|
||||
0.07: added support for bangle.js 2
|
||||
0.08: call setUI before loading widgets to indicate we're a clock
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
Astral Clock
|
||||
============
|
||||
NOTE FOR THE BANGLE 2 THIS APP ONLY SUPPORTS USING THE BLACK BACKGROUND CURRENTLY
|
||||
|
||||
Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting.
|
||||
|
||||

|
||||
|
@ -7,23 +9,21 @@ Clock that calculates and displays Alt Az positions of all planets, Sun as well
|
|||
|
||||
Functions
|
||||
---------
|
||||
**BTN1**: Refreshes Alt/Az readings. The coordinates are NOT continually updated, this is to save resources and battery usage plus it avoids you having to wait for calculations to finish before you can do anything else on the watch - it doesn't take long but it could still be annoying.
|
||||
**BTN2**: Load side-menu as standard for clocks.
|
||||
**BTN3**: Changes between planet mode and extra/other targets - discussed below (will still need to press button 1 after switching to update calcs).
|
||||
**BTN4**: This is the left touchscreen, and when the LCD is on you can use this to change the font between red/white. This will only work after the GPS location has been set initially.
|
||||
Swiping left or right will alternate between planets and other astronomy targets, see below for how to change these addtional ones.
|
||||
|
||||
The text will turn blue during calculation and then back again once complete.
|
||||
The data is refreshed automatically every 2 minutes. You can force a refresh as well by swiping up or, on Bangle 1, pressing Button 3.
|
||||
|
||||
When you first install it, all positions will be estimated from UK as the default location and all the text will be white; from the moment you get your first GPS lock with the clock, it will save your location, recalculate accordingly and change the text to red, ideal for maintaining night vision, the calculations will also now be relevant to your location and time. If you have not used the GPS yet, I suggest using it outside briefly to get your first fix as the initial one can take a bit longer, although it should still just be a minute or 2 max normally.
|
||||
Swiping down will disable/enable the compass and GPS.
|
||||
|
||||
When you first install it, all positions will be estimated from UK as the default location and all the text will be white; from the moment you get your first GPS lock with the clock, it will save your location, recalculate accordingly and change the text to red, ideal for maintaining night vision. One the Bangle.JS 2, the colour will be a light blue rather than red because the colours are not as vibrant. The calculations will also now be relevant to your location and time. If you have not used the GPS yet, I suggest using it outside briefly to get your first fix as the initial one can take a bit longer, although it should still just be a minute or 2 max normally.
|
||||
Lat and Lon are saved in a file called **astral.config**. You can review this file if you want to confirm current coordinates or even hard set different values \- although be careful doing the latter as there is no error handling to manage bad values here so you would have to delete the file and have the app generate a new one if that happens, also the GPS functionality will overwrite anything you put in here once it picks up your location.
|
||||
|
||||
There can currently be a slight error mainly to the Az at times due to a firmware issue for acos (arccosine) that affect spherical calculations but I have used an estimator function that gives a good enough accuracy for general observation so shouldn't noticeably be too far off. I\'ll be implementing acos for better accuracy when the fix is in a standard release and the update will still include the current estimate function to support a level of backward compatibility.
|
||||
|
||||
The moon phases are split into the 8 phases with an image for each - new moon would show no image.
|
||||
|
||||
The compass is displayed above the minute digits, if you get strange values or dashes the compass needs calibration but you just need to move the watch around a bit for this each time - ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or doing Wing Chun Kuen.
|
||||
The compass is displayed on the left.
|
||||
|
||||
Also the compass isn\’t tilt compensated so try and keep the face parallel when taking a reading.
|
||||
Also the compass isn\’t tilt compensated so try and keep the face parallel when taking a reading. It's more of an indicator, for a more accurate compass reading, you can use one of the many great apps in the apploader that compensated for movement and angles of the watch etc.
|
||||
|
||||
Additional Astronomy Targets
|
||||
----------------------------
|
||||
|
@ -37,7 +37,7 @@ The type property is not utilised as yet but relates to whether the object is (i
|
|||
|
||||
Updates & Feedback
|
||||
------------------
|
||||
Put together, initially at least, by \"Ben Jabituya\", https://jabituyaben.wixsite.com/majorinput, jabituyaben@gmail.com. Feel free to get in touch for any feature request. Also I\'m not precious at all - if you know of efficiencies or improvements you could make, just put the changes in. One thing that would probably be ideal is to change some of the functions to inline C to make it faster.
|
||||
Put together, initially at least, by \"Ben Jabituya\", https://majorinput.co.uk, jabituyaben@gmail.com.
|
||||
|
||||
Credit to various sources from which I have literally taken source code and shoehorned to fit on the Bangle:
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
setupcomplete_colour = "#ff3329";
|
||||
default_colour = "#ffffff";
|
||||
calc_display_colour = "#00FFFF";
|
||||
display_colour = default_colour;
|
||||
|
||||
require("Font8x12").add(Graphics);
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
var setupcomplete_colour = "#ff3329";
|
||||
var default_colour = "#ffffff";
|
||||
var calc_display_colour = "#00FFFF";
|
||||
var display_colour = default_colour;
|
||||
|
||||
var processing = false;
|
||||
var all_extras_array = [];
|
||||
|
@ -10,11 +14,19 @@ var mode = "planetary";
|
|||
var modeswitch = false;
|
||||
|
||||
var colours_switched = false;
|
||||
var sensorsOn = false;
|
||||
|
||||
// Load fonts
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
// position on screen
|
||||
const Xaxis = 150, Yaxis = 55;
|
||||
var screenSize = g.getHeight();
|
||||
console.log(screenSize);
|
||||
var Xaxis = 150, Yaxis = 55;
|
||||
if (screenSize <= 176) {
|
||||
setupcomplete_colour = "#00FFFF";
|
||||
Xaxis = 110;
|
||||
Yaxis = 40;
|
||||
}
|
||||
|
||||
//lat lon settings loading
|
||||
var astral_settings;
|
||||
|
@ -443,7 +455,8 @@ function dec2str(x) {
|
|||
var sgn = (x < 0) ? "-" : " ";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return sgn + cintstr(d, 2) + "° " + frealstr(m, 4, 1) + "'";
|
||||
//return sgn + cintstr(d, 2) + "° " + frealstr(m, 4, 1) + "'";
|
||||
return sgn + cintstr(d, 2) + "° ";
|
||||
}
|
||||
|
||||
// return the integer part of a number
|
||||
|
@ -534,14 +547,7 @@ function mean_sidereal_time(lon) {
|
|||
var mst = 280.46061837 + 360.98564736629 * jd
|
||||
+ 0.000387933 * jt * jt - jt * jt * jt / 38710000 + lon;
|
||||
|
||||
if (mst > 0.0) {
|
||||
while (mst > 360.0)
|
||||
mst = mst - 360.0;
|
||||
}
|
||||
else {
|
||||
while (mst < 0.0)
|
||||
mst = mst + 360.0;
|
||||
}
|
||||
mst = mst % 360;
|
||||
return mst;
|
||||
}
|
||||
|
||||
|
@ -559,7 +565,8 @@ function degr2str(x) {
|
|||
var sgn = (x < 0) ? "-" : " ";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return sgn + cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'";
|
||||
//return sgn + cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'";
|
||||
return sgn + cintstr(d, 3) + "° ";
|
||||
}
|
||||
|
||||
// converts latitude in signed degrees into string
|
||||
|
@ -568,7 +575,8 @@ function lat2str(x) {
|
|||
var sgn = (x < 0) ? " S" : " N";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
//return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
return cintstr(d, 3) + "° ";
|
||||
}
|
||||
|
||||
// converts longitude in signed degrees into string
|
||||
|
@ -577,7 +585,8 @@ function lon2str(x) {
|
|||
var sgn = (x < 0) ? " W" : " E";
|
||||
var d = Math.floor(dec);
|
||||
var m = 60 * (dec - d);
|
||||
return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
//return cintstr(d, 3) + "° " + frealstr(m, 4, 1) + "'" + sgn;
|
||||
return cintstr(d, 3) + "° ";
|
||||
}
|
||||
|
||||
// format two digits with leading zero if needed
|
||||
|
@ -649,9 +658,9 @@ function write_refresh_note(colour) {
|
|||
if (!ready_to_compute) {
|
||||
g.drawString("mode change:", Xaxis + 50, cursor, false);
|
||||
cursor += 15;
|
||||
g.drawString("BTN1 to refresh", Xaxis + 50, cursor, true /*clear background*/);
|
||||
g.drawString("swipe up to refresh", Xaxis + 50, cursor, true /*clear background*/);
|
||||
cursor += 15;
|
||||
g.drawString("BTN3 to cancel", Xaxis + 50, cursor, true /*clear background*/);
|
||||
g.drawString("swipe left/right to cancel", Xaxis + 50, cursor, true /*clear background*/);
|
||||
}
|
||||
else
|
||||
g.drawString("updating, please wait", Xaxis + 50, cursor, false);
|
||||
|
@ -659,42 +668,92 @@ function write_refresh_note(colour) {
|
|||
|
||||
function draw_moon(phase) {
|
||||
g.setColor(display_colour);
|
||||
var moonOffset = 12;
|
||||
var mooonRadius = 25;
|
||||
if (screenSize > 176) {
|
||||
if (phase == 5) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(220, 25, 240, 90);
|
||||
}
|
||||
else if (phase == 6) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(200, 25, 240, 90);
|
||||
}
|
||||
else if (phase == 1) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(180, Yaxis, 30);
|
||||
g.fillCircle(180, Yaxis + moonOffset, mooonRadius);
|
||||
}
|
||||
else if (phase == 4)
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
else if (phase == 3) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(160, 25, 180, 90);
|
||||
}
|
||||
else if (phase == 2) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
g.setColor("#000000");
|
||||
g.fillRect(160, 25, 200, 90);
|
||||
}
|
||||
else if (phase == 7) {
|
||||
g.fillCircle(200, Yaxis, 30);
|
||||
g.fillCircle(200, Yaxis + moonOffset, mooonRadius);
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(220, Yaxis, 30);
|
||||
g.fillCircle(220, Yaxis, 25);
|
||||
}
|
||||
}
|
||||
else {
|
||||
moonOffset = 12;
|
||||
//var moonOffsetX = 150;
|
||||
mooonRadius = 17;
|
||||
g.setColor(display_colour);
|
||||
if (phase != 0)
|
||||
g.fillCircle(150, Yaxis + moonOffset, mooonRadius);
|
||||
if (phase == 5) {
|
||||
g.setColor("#000000");
|
||||
g.fillRect(165, 25, 180, 90);
|
||||
}
|
||||
else if (phase == 6) {
|
||||
g.setColor("#000000");
|
||||
g.fillRect(150, 25, 240, 90);
|
||||
}
|
||||
else if (phase == 1) {
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(140, Yaxis + moonOffset, mooonRadius);
|
||||
}
|
||||
else if (phase == 4)
|
||||
g.fillCircle(150, Yaxis + moonOffset, mooonRadius);
|
||||
else if (phase == 3) {
|
||||
g.setColor("#000000");
|
||||
g.fillRect(125, 25, 135, 90);
|
||||
}
|
||||
else if (phase == 2) {
|
||||
g.setColor("#000000");
|
||||
g.fillRect(125, 25, 150, 90);
|
||||
}
|
||||
else if (phase == 7) {
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(160, Yaxis + moonOffset, mooonRadius);
|
||||
}
|
||||
}
|
||||
g.setColor(display_colour);
|
||||
}
|
||||
|
||||
function autoUpdate() {
|
||||
ready_to_compute = true;
|
||||
g.setColor(display_colour);
|
||||
g.fillCircle(15, 160, 5);
|
||||
setTimeout(function () {
|
||||
print("ready");
|
||||
draw();
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
//print("drawing");
|
||||
if (astral_settings.astral_default)
|
||||
display_colour = default_colour;
|
||||
else if (!colours_switched)
|
||||
|
@ -708,19 +767,26 @@ function draw() {
|
|||
g.reset();
|
||||
g.setColor(display_colour);
|
||||
// draw the current time (4x size 7 segment)
|
||||
g.setFont("7x11Numeric7Seg", 5);
|
||||
g.setFont("7x11Numeric7Seg", 4);
|
||||
g.setFontAlign(1, 1); // align right bottom
|
||||
g.drawString(time, Xaxis + 20, Yaxis + 30, true /*clear background*/);
|
||||
g.drawString(time, Xaxis + 23, Yaxis + 34, true /*clear background*/);
|
||||
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(1, 1); // align center bottom
|
||||
// pad the date - this clears the background if the date were to change length
|
||||
var dateStr = " " + require("locale").date(d) + " ";
|
||||
g.drawString(dateStr, Xaxis - 40, Yaxis - 40, true /*clear background*/);
|
||||
|
||||
if (screenSize < 177) {
|
||||
g.drawString(dateStr, 100, 20, true /*clear background*/);
|
||||
}
|
||||
else {
|
||||
//bangle 1
|
||||
g.drawString(dateStr, 150, 40, true /*clear background*/);
|
||||
}
|
||||
|
||||
//compute location of objects
|
||||
g.setFontAlign(1, 1);
|
||||
g.setFont("6x8");
|
||||
g.setFont("6x8", 1);
|
||||
|
||||
if (ready_to_compute)
|
||||
g.setColor(calc_display_colour);
|
||||
|
@ -729,25 +795,15 @@ function draw() {
|
|||
g.setColor("#000000");
|
||||
|
||||
cursor = Yaxis + 50;
|
||||
if (pstrings.length == 0) {
|
||||
if (ready_to_compute)
|
||||
g.drawString("updating, please wait", Xaxis + 50, cursor, true);
|
||||
else
|
||||
g.drawString("press BTN1 to update", Xaxis + 50, cursor, true /*clear background*/);
|
||||
}
|
||||
else {
|
||||
if (pstrings.length != 0) {
|
||||
|
||||
for (let i = 0; i < pstrings.length; i++) {
|
||||
g.drawString(pstrings[i], Xaxis + 50, cursor, true /*clear background*/);
|
||||
cursor += 15;
|
||||
g.drawString(pstrings[i], Xaxis + 40, cursor, true /*clear background*/);
|
||||
cursor += 10;
|
||||
}
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
}
|
||||
|
||||
if (modeswitch)
|
||||
if (ready_to_compute)
|
||||
write_refresh_note(calc_display_colour);
|
||||
else
|
||||
write_refresh_note(display_colour);
|
||||
|
||||
if (ready_to_compute) {
|
||||
processing = true;
|
||||
ready_to_compute = false;
|
||||
|
@ -757,33 +813,44 @@ function draw() {
|
|||
g.fillRect(Xaxis - 150, Yaxis + 40, Xaxis + 200, Yaxis + 200);
|
||||
modeswitch = false;
|
||||
processing = false;
|
||||
Bangle.buzz();
|
||||
//Bangle.buzz();
|
||||
}
|
||||
|
||||
current_moonphase = getMoonPhase();
|
||||
all_extras_array = [];
|
||||
if (sensorsOn) {
|
||||
g.setColor(display_colour);
|
||||
g.fillCircle(160, 160, 5);
|
||||
}
|
||||
}
|
||||
|
||||
function SwitchSensorState() {
|
||||
if (sensorsOn) {
|
||||
print("turning sensors off");
|
||||
Bangle.setCompassPower(0);
|
||||
Bangle.setGPSPower(0);
|
||||
sensorsOn = 0;
|
||||
g.setColor("#000000");
|
||||
g.fillCircle(screenSize - 15, screenSize - 15, 7);
|
||||
//g.drawString(compass_heading, 30, 115, true /*clear background*/);
|
||||
g.fillRect(0, 100, 30, 120);
|
||||
}
|
||||
else {
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
sensorsOn = 1;
|
||||
g.setColor(display_colour);
|
||||
g.fillCircle(screenSize - 15, screenSize - 15, 5);
|
||||
}
|
||||
}
|
||||
|
||||
g.clear();
|
||||
g.setColor("#000000");
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.fillRect(0, 0, 175, 175);
|
||||
current_moonphase = getMoonPhase();
|
||||
|
||||
Bangle.setUI("clockupdown", btn => {
|
||||
if (btn==0) {
|
||||
if (!processing) {
|
||||
if (!modeswitch) {
|
||||
modeswitch = true;
|
||||
if (mode == "planetary") mode = "extras";
|
||||
else mode = "planetary";
|
||||
}
|
||||
else
|
||||
modeswitch = false;
|
||||
}
|
||||
} else {
|
||||
if (!processing)
|
||||
ready_to_compute = true;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
|
@ -792,59 +859,70 @@ Bangle.drawWidgets();
|
|||
draw_moon(current_moonphase);
|
||||
draw();
|
||||
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower', on => {
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
Bangle.setCompassPower(0);
|
||||
if (!astral_settings.astral_default)
|
||||
Bangle.setGPSPower(0);
|
||||
if (on) {
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
if (current_moonphase !== undefined) {
|
||||
draw_moon(current_moonphase);
|
||||
}
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
var updateInterval = setInterval(autoUpdate, 120000);
|
||||
//var magInterval = setInterval(updateMag, 50);
|
||||
|
||||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
|
||||
var secondInterval;
|
||||
|
||||
autoUpdate();
|
||||
|
||||
setWatch(SwitchSensorState, BTN1, { repeat: true });
|
||||
if(process.env.HWVERSION != 2)
|
||||
setWatch(autoUpdate, BTN3, { repeat: true });
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setClockMode();
|
||||
//Bangle.setClockMode();
|
||||
|
||||
setWatch(function () {
|
||||
if (!astral_settings.astral_default) {
|
||||
colours_switched = true;
|
||||
if (display_colour == setupcomplete_colour)
|
||||
display_colour = default_colour;
|
||||
else
|
||||
display_colour = setupcomplete_colour;
|
||||
draw_moon(current_moonphase);
|
||||
Bangle.on("swipe", function (directionLR, directionUD) {
|
||||
if (!processing) {
|
||||
if (-1 == directionUD) {
|
||||
g.setColor(display_colour);
|
||||
g.fillCircle(15, 160, 5);
|
||||
ready_to_compute = true;
|
||||
}
|
||||
else if (-1 == directionLR || directionLR == 1) {
|
||||
print("attempting mode switch");
|
||||
if (mode == "planetary") mode = "extras";
|
||||
else mode = "planetary";
|
||||
g.setColor(display_colour);
|
||||
g.fillCircle(15, 160, 5);
|
||||
ready_to_compute = true;
|
||||
}
|
||||
else if (directionUD == 1) {
|
||||
SwitchSensorState();
|
||||
}
|
||||
}, BTN4, { repeat: true });
|
||||
|
||||
setTimeout(function () {
|
||||
print("ready");
|
||||
draw();
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
//events
|
||||
Bangle.on('mag', function (m) {
|
||||
g.setFont("6x8",2);
|
||||
if (isNaN(m.heading))
|
||||
compass_heading = "---";
|
||||
else
|
||||
if (!isNaN(m.heading)) {
|
||||
compass_heading = Math.round(m.heading);
|
||||
// g.setColor("#000000");
|
||||
// g.fillRect(160, 10, 160, 20);
|
||||
g.setColor(display_colour);
|
||||
if (compass_heading < 100)
|
||||
compass_heading = " " + compass_heading;
|
||||
g.drawString(compass_heading, 150, 20, true /*clear background*/);
|
||||
if (sensorsOn) {
|
||||
g.setFont("8x12");
|
||||
g.setFontAlign(1, 1);
|
||||
g.drawString(compass_heading, 25, 112, true /*clear background*/);
|
||||
}
|
||||
}
|
||||
//var n = magArray.length;
|
||||
//var mean = Math.round( magArray.reduce((a, b) => a + b) / n);
|
||||
//compass_heading = mean;
|
||||
});
|
||||
|
||||
Bangle.on('GPS', function (g) {
|
||||
if (g.fix) {
|
||||
display_colour = setupcomplete_colour;
|
||||
astral_settings.lat = g.lat;
|
||||
astral_settings.lon = g.lon;
|
||||
astral_settings.astral_default = false;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"id": "astral",
|
||||
"name": "Astral Clock",
|
||||
"version": "0.05",
|
||||
"version": "0.08",
|
||||
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"astral.app.js","url":"app.js"},
|
||||
{"name":"astral.img","url":"app-icon.js","evaluate":true}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Don't fire if the app uses swipes already.
|
||||
0.03: Only count defined handlers in the handler array.
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
if (Bangle["#on"+eventType] === undefined) {
|
||||
return 0;
|
||||
} else if (Bangle["#on"+eventType] instanceof Array) {
|
||||
return Bangle["#on"+eventType].length;
|
||||
return Bangle["#on"+eventType].filter(x=>x).length;
|
||||
} else if (Bangle["#on"+eventType] !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "backswipe",
|
||||
"name": "Back Swipe",
|
||||
"shortName":"BackSwipe",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
|
||||
"icon": "app.png",
|
||||
"tags": "back,gesture,swipe",
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Improve current time readability in light theme.
|
||||
0.05: Show calendar colors & improved all day events.
|
||||
0.06: Improved multi-line locations & titles
|
||||
0.07: Buzz 30, 15 and 1 minute before an event
|
||||
|
|
|
@ -117,6 +117,17 @@ function fullRedraw() {
|
|||
drawFutureEvents(y);
|
||||
}
|
||||
|
||||
function buzzForEvents() {
|
||||
let nextEvent = next[0]; if (!nextEvent) return;
|
||||
if (nextEvent.allDay) return;
|
||||
let minToEvent = Math.round((nextEvent.timestamp - getTime()) / 60.0);
|
||||
switch (minToEvent) {
|
||||
case 30: require("buzz").pattern(","); break;
|
||||
case 15: require("buzz").pattern(", ,"); break;
|
||||
case 1: require("buzz").pattern(": : :"); break;
|
||||
}
|
||||
}
|
||||
|
||||
function redraw() {
|
||||
g.reset();
|
||||
if (current.find(e=>!isActive(e)) || next.find(isActive)) {
|
||||
|
@ -124,10 +135,12 @@ function redraw() {
|
|||
} else {
|
||||
drawCurrentEvents(30);
|
||||
}
|
||||
buzzForEvents();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
fullRedraw();
|
||||
buzzForEvents();
|
||||
var minuteInterval = setInterval(redraw, 60 * 1000);
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "calclock",
|
||||
"name": "Calendar Clock",
|
||||
"shortName": "CalClock",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
||||
"icon": "calclock.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -15,3 +15,5 @@
|
|||
Display events for current month on touch
|
||||
0.14: Add support for holidays
|
||||
0.15: Edit holidays on device in settings
|
||||
0.16: Add menu to fast open settings to edit holidays
|
||||
Display Widgets in menus
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Calendar
|
||||
|
||||
Basic calendar
|
||||
Monthly calendar, displays holidays uploaded from the web interface and scheduled events.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -14,4 +14,4 @@ Basic calendar
|
|||
|
||||
## Settings
|
||||
|
||||
- B2 Colors: use non-dithering colors (default, recommended for Bangle 2) or the original color scheme.
|
||||
B2 Colors: use non-dithering colors (default, recommended for Bangle 2) or the original color scheme.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
const maxX = g.getWidth();
|
||||
const maxY = g.getHeight();
|
||||
const fontSize = g.getWidth() > 200 ? 2 : 1;
|
||||
|
@ -17,22 +18,33 @@ const red = "#d41706";
|
|||
const blue = "#0000ff";
|
||||
const yellow = "#ffff00";
|
||||
const cyan = "#00ffff";
|
||||
let bgColor = color4;
|
||||
let bgColorMonth = color1;
|
||||
let bgColorDow = color2;
|
||||
let bgColorWeekend = color3;
|
||||
let fgOtherMonth = gray1;
|
||||
let fgSameMonth = white;
|
||||
let bgEvent = blue;
|
||||
let bgOtherEvent = "#ff8800";
|
||||
let bgColor;
|
||||
let bgColorMonth;
|
||||
let bgColorDow;
|
||||
let bgColorWeekend;
|
||||
let fgOtherMonth;
|
||||
let fgSameMonth;
|
||||
let bgEvent;
|
||||
let bgOtherEvent;
|
||||
const eventsPerDay=6; // how much different events per day we can display
|
||||
const date = new Date();
|
||||
|
||||
const timeutils = require("time_utils");
|
||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||
let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
|
||||
let events;
|
||||
const dowLbls = function() {
|
||||
const locale = require('locale').name;
|
||||
const days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
|
||||
const d = new Date();
|
||||
return days.map(i => {
|
||||
d.setDate(d.getDate() + (i + 7 - d.getDay()) % 7);
|
||||
return require("locale").dow(d, 1);
|
||||
});
|
||||
}();
|
||||
|
||||
const loadEvents = () => {
|
||||
// all alarms that run on a specific date
|
||||
const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
|
||||
events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a.on && a.date).map(a => {
|
||||
const date = new Date(a.date);
|
||||
const time = timeutils.decodeTime(a.t);
|
||||
date.setHours(time.h);
|
||||
|
@ -49,11 +61,13 @@ const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a
|
|||
}
|
||||
events.push(o);
|
||||
});
|
||||
};
|
||||
|
||||
const loadSettings = () => {
|
||||
let settings = require('Storage').readJSON("calendar.json", true) || {};
|
||||
if (settings.ndColors === undefined) {
|
||||
settings.ndColors = !g.theme.dark;
|
||||
}
|
||||
|
||||
if (settings.ndColors === true) {
|
||||
bgColor = white;
|
||||
bgColorMonth = blue;
|
||||
|
@ -63,25 +77,26 @@ if (settings.ndColors === true) {
|
|||
fgSameMonth = black;
|
||||
bgEvent = color2;
|
||||
bgOtherEvent = cyan;
|
||||
} else {
|
||||
bgColor = color4;
|
||||
bgColorMonth = color1;
|
||||
bgColorDow = color2;
|
||||
bgColorWeekend = color3;
|
||||
fgOtherMonth = gray1;
|
||||
fgSameMonth = white;
|
||||
bgEvent = blue;
|
||||
bgOtherEvent = "#ff8800";
|
||||
}
|
||||
};
|
||||
|
||||
function getDowLbls(locale) {
|
||||
let days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
|
||||
const d = new Date();
|
||||
return days.map(i => {
|
||||
d.setDate(d.getDate() + (i + 7 - d.getDay()) % 7);
|
||||
return require("locale").dow(d, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function sameDay(d1, d2) {
|
||||
const sameDay = function(d1, d2) {
|
||||
"jit";
|
||||
return d1.getFullYear() === d2.getFullYear() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getDate() === d2.getDate();
|
||||
}
|
||||
};
|
||||
|
||||
function drawEvent(ev, curDay, x1, y1, x2, y2) {
|
||||
const drawEvent = function(ev, curDay, x1, y1, x2, y2) {
|
||||
"ram";
|
||||
switch(ev.type) {
|
||||
case "e": // alarm/event
|
||||
|
@ -99,9 +114,33 @@ function drawEvent(ev, curDay, x1, y1, x2, y2) {
|
|||
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function drawCalendar(date) {
|
||||
const calcDays = (month, monthMaxDayMap, dowNorm) => {
|
||||
"jit";
|
||||
const maxDay = colN * (rowN - 1) + 1;
|
||||
const days = [];
|
||||
let nextMonthDay = 1;
|
||||
let thisMonthDay = 51;
|
||||
const month2 = month;
|
||||
let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
|
||||
|
||||
for (let i = 0; i < maxDay; i++) {
|
||||
if (i < dowNorm) {
|
||||
days.push(prevMonthDay);
|
||||
prevMonthDay++;
|
||||
} else if (thisMonthDay <= monthMaxDayMap[month] + 50) {
|
||||
days.push(thisMonthDay);
|
||||
thisMonthDay++;
|
||||
} else {
|
||||
days.push(nextMonthDay);
|
||||
nextMonthDay++;
|
||||
}
|
||||
}
|
||||
return days;
|
||||
};
|
||||
|
||||
const drawCalendar = function(date) {
|
||||
g.setBgColor(bgColor);
|
||||
g.clearRect(0, 0, maxX, maxY);
|
||||
g.setBgColor(bgColorMonth);
|
||||
|
@ -139,7 +178,6 @@ function drawCalendar(date) {
|
|||
true
|
||||
);
|
||||
|
||||
let dowLbls = getDowLbls(require('locale').name);
|
||||
dowLbls.forEach((lbl, i) => {
|
||||
g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2);
|
||||
});
|
||||
|
@ -163,23 +201,7 @@ function drawCalendar(date) {
|
|||
11: 31
|
||||
};
|
||||
|
||||
let days = [];
|
||||
let nextMonthDay = 1;
|
||||
let thisMonthDay = 51;
|
||||
let prevMonthDay = monthMaxDayMap[month > 0 ? month - 1 : 11] - dowNorm + 1;
|
||||
for (let i = 0; i < colN * (rowN - 1) + 1; i++) {
|
||||
if (i < dowNorm) {
|
||||
days.push(prevMonthDay);
|
||||
prevMonthDay++;
|
||||
} else if (thisMonthDay <= monthMaxDayMap[month] + 50) {
|
||||
days.push(thisMonthDay);
|
||||
thisMonthDay++;
|
||||
} else {
|
||||
days.push(nextMonthDay);
|
||||
nextMonthDay++;
|
||||
}
|
||||
}
|
||||
|
||||
const days = calcDays(month, monthMaxDayMap, dowNorm);
|
||||
const weekBeforeMonth = new Date(date.getTime());
|
||||
weekBeforeMonth.setDate(weekBeforeMonth.getDate() - 7);
|
||||
const week2AfterMonth = new Date(date.getFullYear(), date.getMonth()+1, 0);
|
||||
|
@ -189,8 +211,15 @@ function drawCalendar(date) {
|
|||
ev.date.setFullYear(ev.date.getMonth() < 6 ? week2AfterMonth.getFullYear() : weekBeforeMonth.getFullYear());
|
||||
}
|
||||
});
|
||||
const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
|
||||
eventsThisMonth.sort((a,b) => a.date - b.date);
|
||||
|
||||
const eventsThisMonthPerDay = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth).reduce((acc, ev) => {
|
||||
const day = ev.date.getDate();
|
||||
if (!acc[day]) {
|
||||
acc[day] = [];
|
||||
}
|
||||
acc[day].push(ev);
|
||||
return acc;
|
||||
}, []);
|
||||
let i = 0;
|
||||
g.setFont("8x12", fontSize);
|
||||
for (y = 0; y < rowN - 1; y++) {
|
||||
|
@ -205,13 +234,13 @@ function drawCalendar(date) {
|
|||
const x2 = x * colW + colW;
|
||||
const y2 = y * rowH + headerH + rowH + rowH;
|
||||
|
||||
if (eventsThisMonth.length > 0) {
|
||||
const eventsThisDay = eventsThisMonthPerDay[curDay.getDate()];
|
||||
if (eventsThisDay && eventsThisDay.length > 0) {
|
||||
// Display events for this day
|
||||
eventsThisMonth.forEach((ev, idx) => {
|
||||
eventsThisDay.forEach((ev, idx) => {
|
||||
if (sameDay(ev.date, curDay)) {
|
||||
drawEvent(ev, curDay, x1, y1, x2, y2);
|
||||
|
||||
eventsThisMonth.splice(idx, 1); // this event is no longer needed
|
||||
eventsThisDay.splice(idx, 1); // this event is no longer needed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -235,9 +264,44 @@ function drawCalendar(date) {
|
|||
);
|
||||
} // end for (x = 0; x < colN; x++)
|
||||
} // end for (y = 0; y < rowN - 1; y++)
|
||||
} // end function drawCalendar
|
||||
}; // end function drawCalendar
|
||||
|
||||
const showMenu = function() {
|
||||
const menu = {
|
||||
"" : {
|
||||
title : "Calendar",
|
||||
remove: () => {
|
||||
require("widget_utils").show();
|
||||
}
|
||||
},
|
||||
"< Back": () => {
|
||||
require("widget_utils").hide();
|
||||
E.showMenu();
|
||||
setUI();
|
||||
},
|
||||
/*LANG*/"Exit": () => load(),
|
||||
/*LANG*/"Settings": () => {
|
||||
const appSettings = eval(require('Storage').read('calendar.settings.js'));
|
||||
appSettings(() => {
|
||||
loadSettings();
|
||||
loadEvents();
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
};
|
||||
if (require("Storage").read("alarm.app.js")) {
|
||||
menu[/*LANG*/"Launch Alarms"] = () => {
|
||||
load("alarm.app.js");
|
||||
};
|
||||
}
|
||||
require("widget_utils").show();
|
||||
E.showMenu(menu);
|
||||
};
|
||||
|
||||
const setUI = function() {
|
||||
require("widget_utils").hide(); // No space for widgets!
|
||||
drawCalendar(date);
|
||||
|
||||
function setUI() {
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
swipe: (dirLR, dirUD) => {
|
||||
|
@ -261,7 +325,14 @@ function setUI() {
|
|||
drawCalendar(date);
|
||||
}
|
||||
},
|
||||
btn: (n) => n === (process.env.HWVERSION === 2 ? 1 : 3) && load(),
|
||||
btn: (n) => {
|
||||
if (process.env.HWVERSION === 2 || n === 2) {
|
||||
showMenu();
|
||||
} else if (n === 3) {
|
||||
// directly exit only on Bangle.js 1
|
||||
load();
|
||||
}
|
||||
},
|
||||
touch: (n,e) => {
|
||||
events.sort((a,b) => a.date - b.date);
|
||||
const menu = events.filter(ev => ev.date.getFullYear() === date.getFullYear() && ev.date.getMonth() === date.getMonth()).map(e => {
|
||||
|
@ -274,16 +345,19 @@ function setUI() {
|
|||
}
|
||||
menu[""] = { title: require("locale").month(date) + " " + date.getFullYear() };
|
||||
menu["< Back"] = () => {
|
||||
require("widget_utils").hide();
|
||||
E.showMenu();
|
||||
drawCalendar(date);
|
||||
setUI();
|
||||
};
|
||||
require("widget_utils").show();
|
||||
E.showMenu(menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadSettings();
|
||||
loadEvents();
|
||||
Bangle.loadWidgets();
|
||||
require("Font8x12").add(Graphics);
|
||||
drawCalendar(date);
|
||||
setUI();
|
||||
// No space for widgets!
|
||||
}
|
||||
|
|
|
@ -178,9 +178,8 @@ function getData() {
|
|||
})()\n`, contents => {
|
||||
const fileNames = JSON.parse(contents);
|
||||
if (fileNames.length > 0) {
|
||||
Util.readStorage('calendar.days.json',data=>{
|
||||
holidays = JSON.parse(data || "[]") || [];
|
||||
|
||||
Util.readStorageJSON('calendar.days.json',data=>{
|
||||
holidays = data || [];
|
||||
Util.hideModal();
|
||||
render();
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.15",
|
||||
"description": "Simple calendar",
|
||||
"version": "0.16",
|
||||
"description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.",
|
||||
"icon": "calendar.png",
|
||||
"screenshots": [{"url":"screenshot_calendar.png"}],
|
||||
"tags": "calendar,tool",
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
(function (back) {
|
||||
var FILE = "calendar.json";
|
||||
const FILE = "calendar.json";
|
||||
const HOLIDAY_FILE = "calendar.days.json";
|
||||
var settings = require('Storage').readJSON(FILE, true) || {};
|
||||
if (settings.ndColors === undefined)
|
||||
const settings = require('Storage').readJSON(FILE, true) || {};
|
||||
if (settings.ndColors === undefined) {
|
||||
if (process.env.HWVERSION == 2) {
|
||||
settings.ndColors = true;
|
||||
} else {
|
||||
settings.ndColors = false;
|
||||
}
|
||||
const holidays = require("Storage").readJSON(HOLIDAY_FILE,1).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
|
||||
}
|
||||
const holidays = (require("Storage").readJSON(HOLIDAY_FILE,1)||[]).sort((a,b) => new Date(a.date) - new Date(b.date)) || [];
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Simple app to display loyalty cards
|
||||
0.02: Hiding widgets while showing the code
|
||||
|
|
|
@ -19,33 +19,11 @@ This app is a proof of concept, many codes are too complex to be rendered by the
|
|||
|
||||
### How to sync
|
||||
|
||||
_WIP: we currently cannot synchronize cards, a PR is under review in GadgetBridge repo, soon we will see support on nightly builds_
|
||||
We can synchronize cards with GadgetBridge and Catima, refer to those projects for further information.
|
||||
The feature is currently available on nightly builds only.
|
||||
It should be released from version 0.77 (not yet out at the time of writing).
|
||||
|
||||
You can test it by sending on your bangle a file like this:
|
||||
|
||||
_android.cards.json_
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "First card",
|
||||
"value": "01234",
|
||||
"note": "Some stuff",
|
||||
"type": "CODE_39",
|
||||
"balance": "15 EUR",
|
||||
"expiration": "1691102081"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Second card",
|
||||
"value": "Hello world",
|
||||
"note": "This is a qr generated on the bangle!",
|
||||
"type": "QR_CODE",
|
||||
"balance": "2 P"
|
||||
}
|
||||
]
|
||||
```
|
||||
GadgetBridge syncronizes all cards at once, if you have too many cards you may want to explicitly select which ones to syncronize, keep in mind the limitations of the Banglejs.
|
||||
|
||||
### Credits
|
||||
|
||||
|
|
|
@ -18,9 +18,8 @@ Bangle.drawWidgets();
|
|||
const WHITE=-1
|
||||
const BLACK=0
|
||||
|
||||
var FILE = "android.cards.json";
|
||||
|
||||
var Locale = require("locale");
|
||||
const Locale = require("locale");
|
||||
const widget_utils = require('widget_utils');
|
||||
|
||||
var fontSmall = "6x8";
|
||||
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
|
||||
|
@ -90,6 +89,7 @@ function printLinearCode(binary) {
|
|||
}
|
||||
|
||||
function showCode(card) {
|
||||
widget_utils.hide();
|
||||
E.showScroller();
|
||||
// keeping it on rising edge would come back twice..
|
||||
setWatch(()=>showCard(card), BTN, {edge:"falling"});
|
||||
|
@ -151,6 +151,7 @@ function showCard(card) {
|
|||
var titleColor = g.theme.fg2;
|
||||
if (card.color)
|
||||
titleColor = isLight(titleBgColor) ? BLACK : WHITE;
|
||||
widget_utils.show();
|
||||
E.showScroller({
|
||||
h : g.getFontHeight(), // height of each menu item in pixels
|
||||
c : lines.length, // number of menu items
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "cards",
|
||||
"name": "Cards",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Display loyalty cards",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: added settings options to change date format
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
(function() {
|
||||
require("Font4x8Numeric").add(Graphics);
|
||||
|
||||
var settings = require("Storage").readJSON("clkinfocal.json",1)||{};
|
||||
settings.fmt = settings.fmt||"DDD";
|
||||
|
||||
var getDateString = function(dt) {
|
||||
switch(settings.fmt) {
|
||||
case "dd MMM":
|
||||
return '' + dt.getDate() + ' ' + require("locale").month(dt,1).toUpperCase();
|
||||
case "DDD dd":
|
||||
return require("locale").dow(dt,1).toUpperCase() + ' ' + dt.getDate();
|
||||
default: // DDD
|
||||
return require("locale").dow(dt,1).toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [
|
||||
|
@ -10,7 +25,7 @@
|
|||
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
||||
return {
|
||||
text : require("locale").dow(d,1).toUpperCase(),
|
||||
text : getDateString(d),
|
||||
img : g.asImage("string")
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
{ "id": "clkinfocal",
|
||||
"name": "Calendar Clockinfo",
|
||||
"version":"0.01",
|
||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday",
|
||||
"version":"0.02",
|
||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo,calendar",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"clkinfocal.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
{"name":"clkinfocal.clkinfo.js","url":"clkinfo.js"},
|
||||
{"name":"clkinfocal.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"clkinfocal.json"}]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "clkinfocal.json";
|
||||
|
||||
// initialize with default settings...
|
||||
let s = {'fmt': 0};
|
||||
|
||||
// and overwrite them with any saved values
|
||||
// this way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||
const saved = settings || {};
|
||||
for (const key in saved) {
|
||||
s[key] = saved[key];
|
||||
}
|
||||
|
||||
function save() {
|
||||
settings = s;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
var date_options = ["DDD","DDD dd","dd MMM"];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'Cal Clkinfo' },
|
||||
'< Back': back,
|
||||
'Format': {
|
||||
value: 0 | date_options.indexOf(s.fmt),
|
||||
min: 0, max: 2,
|
||||
format: v => date_options[v],
|
||||
onchange: v => {
|
||||
s.fmt = date_options[v];
|
||||
save();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
After Width: | Height: | Size: 929 B |
|
@ -0,0 +1,27 @@
|
|||
(function() {
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [
|
||||
{ name : "Clock",
|
||||
get : () => {
|
||||
return {
|
||||
text : require("locale").time(new Date(),1),
|
||||
img : atob("FhaBAAAAAAPwAD/wA8DwHADgYMGDAwMMDAxgMBmAwGYDAZgOBmAcGYA4YwBjDAAMGABgcAOA8DwA/8AA/AAAAAA=")
|
||||
};
|
||||
},
|
||||
show : function() {
|
||||
this.interval = setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
this.interval = setInterval(()=>{
|
||||
this.emit("redraw");
|
||||
}, 60000);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
},
|
||||
hide : function() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "clkinfoclk",
|
||||
"name": "Clockinfo Clock",
|
||||
"version":"0.01",
|
||||
"description": "This displays a clock *inside* a ClockInfo. This can be really handy for the [Clock Info Widget](https://banglejs.com/apps/?id=widclkinfo) where you might want the option to show a clock in the top bar of a non-clock app.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"clkinfoclk.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,14 @@
|
|||
# Clock Switcher
|
||||
|
||||
This switches the default clock.
|
||||
The idea is that you can use this app in combination with e.g. the
|
||||
[Pattern Launcher](?q=ptlaunch) as a quick toggle, instead of navigating through
|
||||
the settings menu.
|
||||
|
||||
## Usage
|
||||
|
||||
Load the app to switch to your next installed clock.
|
||||
|
||||
## Creator
|
||||
|
||||
Richard de Boer (rigrig)
|
|
@ -0,0 +1,23 @@
|
|||
const storage = require('Storage');
|
||||
const clocks = storage.list(/\.info$/)
|
||||
.map(app => {
|
||||
const a=storage.readJSON(app, 1);
|
||||
return (a && a.type == "clock") ? a : undefined;
|
||||
})
|
||||
.filter(app => app) // filter out any undefined apps
|
||||
.sort((a, b) => a.sortorder - b.sortorder)
|
||||
.map(app => app.src);
|
||||
if (clocks.length<1) {
|
||||
E.showAlert(/*LANG*/"No clocks found!", "Clock Switcher")
|
||||
.then(load);
|
||||
} else if (clocks.length<2) {
|
||||
E.showAlert(/*LANG*/"Nothing to do:\nOnly one clock installed!", "Clock Switcher")
|
||||
.then(load);
|
||||
} else {
|
||||
let settings = storage.readJSON('setting.json',true)||{clock:null};
|
||||
const old = clocks.indexOf(settings.clock),
|
||||
next = (old+1)%clocks.length;
|
||||
settings.clock = clocks[next];
|
||||
storage.writeJSON('setting.json', settings);
|
||||
setTimeout(load, 100); // storage.writeJSON needs some time to complete
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AClhCyoAYsIwuF4IwtF4Qxqw2GF4mG1YsqAAeF1eyAAIteFhAvHGLeGwouLR4IuEGDJcGwooBAweH6/X6wwGGKtbKownB640C1gGCAAQwZLgotDF4WG6wuFMZAuVw2yEgqLCABIuD1eGF6eGExYwLw4bCF1BuCDgWFdaGFRgwAJlb0HJogvPdQoAKq0AlYJG1YwDRr+sgEAL4wABwxgNF4ZeSqwLIMAYvNwpebAAOFSBgMCw7sQLxSQORwZLKLw4OLSBlbBgWyLznX2RfPLqBeM6/WcQYvZldbrYvN64jDF7rRNF7qPDGBqPLd6YxDGBTvQPpowQ1YvLGAeHF54wDlYMIwwvPwovQGAIuJ6+FdxSQF1YwRABKONF4mGF7aONAANbMDpeDRxRgFsOyFy+yP4gvLMAiRX6yNDwouMGDYuELxyRGwySS2QuUMAr0SdQguSGA+G1gtMLgguUGAQxFwuH1aWE2QsBwoQEFyzEHAB+FFzAwCMQoALFrRiRwwtefI5mCQwIslAH4A/AFw"))
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "clockswitch",
|
||||
"name": "Clock Switcher",
|
||||
"shortName":"Switch Clock",
|
||||
"version":"0.01",
|
||||
"description": "Switch to the next installed clock",
|
||||
"icon": "icon.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"clockswitch.app.js","url":"app.js"},
|
||||
{"name":"clockswitch.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,29 @@
|
|||
# Contacts
|
||||
|
||||
This app provides a common way to set up the `contacts.json` file.
|
||||
|
||||
## Contacts JSON file
|
||||
|
||||
When the app is loaded from the app loader, a file named
|
||||
`contacts.json` is loaded along with the javascript etc. The file
|
||||
has the following contents:
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"name":"NONE"
|
||||
},
|
||||
{
|
||||
"name":"First Last",
|
||||
"number":"123456789",
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Contacts Editor
|
||||
|
||||
Clicking on the download icon of `Contents` in the app loader invokes
|
||||
the contact editor. The editor downloads and displays the current
|
||||
`contacts.json` file. Clicking the `Edit` button beside an entry
|
||||
causes the entry to be deleted from the list and displayed in the edit
|
||||
boxes. It can be restored - by clicking the `Add` button.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcBkmSpIC/AVsJCJ+AQaCZBCOeACKGQLKGQBA0ggARPJ4IRsYo0ggR9IoAIGiRiIpEECJsAiACBBYoRGpEAI4JBFI47CBLIRlDHYJrGYQIRCwQICL4MQOgx9GboUSeQ4RFwAFBiSGHCIo4CiVIWZyPICP4RaRIQROgARHdIwICoIIFkDpGBAKqHgGACI0AyVIggIDoEEMQ1ICINJCIj4CfwIREBwUgQYYOCfYoFDJQKDFCIopEO4RoDKAqJHRhAC/ATA="))
|
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,189 @@
|
|||
/* contacts.js */
|
||||
|
||||
var Layout = require("Layout");
|
||||
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
|
||||
var wp = require('Storage').readJSON("contacts.json", true) || [];
|
||||
// Use this with corrupted contacts
|
||||
//var wp = [];
|
||||
|
||||
var key; /* Shared between functions, typically wp name */
|
||||
|
||||
function writeContact() {
|
||||
require('Storage').writeJSON("contacts.json", wp);
|
||||
}
|
||||
|
||||
function mainMenu() {
|
||||
var menu = {
|
||||
"< Back" : Bangle.load
|
||||
};
|
||||
if (Object.keys(wp).length==0) Object.assign(menu, {"NO Contacts":""});
|
||||
else for (let id in wp) {
|
||||
let i = id;
|
||||
menu[wp[id]["name"]]=()=>{ decode(i); };
|
||||
}
|
||||
menu["Add"]=addCard;
|
||||
menu["Remove"]=removeCard;
|
||||
g.clear();
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function decode(pin) {
|
||||
var i = wp[pin];
|
||||
var l = i["name"] + "\n" + i["number"];
|
||||
var la = new Layout ({
|
||||
type:"v", c: [
|
||||
{type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: l},
|
||||
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}}
|
||||
], lazy:true});
|
||||
g.clear();
|
||||
la.render();
|
||||
}
|
||||
|
||||
function showNumpad(text, key_, callback) {
|
||||
key = key_;
|
||||
E.showMenu();
|
||||
function addDigit(digit) {
|
||||
key+=digit;
|
||||
if (1) {
|
||||
l = text[key.length];
|
||||
switch (l) {
|
||||
case '.': case ' ': case "'":
|
||||
key+=l;
|
||||
break;
|
||||
case 'd': case 'D': default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
Bangle.buzz(20);
|
||||
update();
|
||||
}
|
||||
function update() {
|
||||
g.reset();
|
||||
g.clearRect(0,0,g.getWidth(),23);
|
||||
s = key + text.substr(key.length, 999);
|
||||
g.setFont("Vector:24").setFontAlign(1,0).drawString(s,g.getWidth(),12);
|
||||
}
|
||||
ds="12%";
|
||||
var numPad = new Layout ({
|
||||
type:"v", c: [{
|
||||
type:"v", c: [
|
||||
{type:"", height:24},
|
||||
{type:"h",filly:1, c: [
|
||||
{type:"btn", font:ds, width:58, label:"7", cb:l=>{addDigit("7");}},
|
||||
{type:"btn", font:ds, width:58, label:"8", cb:l=>{addDigit("8");}},
|
||||
{type:"btn", font:ds, width:58, label:"9", cb:l=>{addDigit("9");}}
|
||||
]},
|
||||
{type:"h",filly:1, c: [
|
||||
{type:"btn", font:ds, width:58, label:"4", cb:l=>{addDigit("4");}},
|
||||
{type:"btn", font:ds, width:58, label:"5", cb:l=>{addDigit("5");}},
|
||||
{type:"btn", font:ds, width:58, label:"6", cb:l=>{addDigit("6");}}
|
||||
]},
|
||||
{type:"h",filly:1, c: [
|
||||
{type:"btn", font:ds, width:58, label:"1", cb:l=>{addDigit("1");}},
|
||||
{type:"btn", font:ds, width:58, label:"2", cb:l=>{addDigit("2");}},
|
||||
{type:"btn", font:ds, width:58, label:"3", cb:l=>{addDigit("3");}}
|
||||
]},
|
||||
{type:"h",filly:1, c: [
|
||||
{type:"btn", font:ds, width:58, label:"0", cb:l=>{addDigit("0");}},
|
||||
{type:"btn", font:ds, width:58, label:"C", cb:l=>{key=key.slice(0,-1); update();}},
|
||||
{type:"btn", font:ds, width:58, id:"OK", label:"OK", cb:callback}
|
||||
]}
|
||||
]}
|
||||
], lazy:true});
|
||||
g.clear();
|
||||
numPad.render();
|
||||
update();
|
||||
}
|
||||
|
||||
function removeCard() {
|
||||
var menu = {
|
||||
"" : {title : "Select Contact"},
|
||||
"< Back" : mainMenu
|
||||
};
|
||||
if (Object.keys(wp).length==0) Object.assign(menu, {"No Contacts":""});
|
||||
else {
|
||||
wp.forEach((val, card) => {
|
||||
const name = wp[card].name;
|
||||
menu[name]=()=>{
|
||||
E.showMenu();
|
||||
var confirmRemove = new Layout (
|
||||
{type:"v", c: [
|
||||
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:"Delete"},
|
||||
{type:"txt", font:"15%", pad:1, fillx:1, filly:1, label:name},
|
||||
{type:"h", c: [
|
||||
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
|
||||
wp.splice(card, 1);
|
||||
writeContact();
|
||||
mainMenu();
|
||||
}},
|
||||
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{mainMenu();}}
|
||||
]}
|
||||
], lazy:true});
|
||||
g.clear();
|
||||
confirmRemove.render();
|
||||
};
|
||||
});
|
||||
}
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function askPosition(callback) {
|
||||
let full = "";
|
||||
showNumpad("dddDDDddd", "", function() {
|
||||
callback(key, "");
|
||||
});
|
||||
}
|
||||
|
||||
function createContact(lat, name) {
|
||||
let n = {};
|
||||
n["name"] = name;
|
||||
n["number"] = lat;
|
||||
wp.push(n);
|
||||
print("add -- contacts", wp);
|
||||
writeContact();
|
||||
}
|
||||
|
||||
function addCardName2(key) {
|
||||
g.clear();
|
||||
askPosition(function(lat, lon) {
|
||||
print("position -- ", lat, lon);
|
||||
createContact(lat, result);
|
||||
mainMenu();
|
||||
});
|
||||
}
|
||||
|
||||
function addCardName(key) {
|
||||
result = key;
|
||||
if (wp[result]!=undefined) {
|
||||
E.showMenu();
|
||||
var alreadyExists = new Layout (
|
||||
{type:"v", c: [
|
||||
{type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result},
|
||||
{type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
|
||||
{type:"h", c: [
|
||||
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addCardName2(key); }},
|
||||
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
|
||||
]}
|
||||
], lazy:true});
|
||||
g.clear();
|
||||
alreadyExists.render();
|
||||
return;
|
||||
}
|
||||
addCardName2(key);
|
||||
}
|
||||
|
||||
function addCard() {
|
||||
require("textinput").input({text:""}).then(result => {
|
||||
if (result != "") {
|
||||
addCardName(result);
|
||||
} else
|
||||
mainMenu();
|
||||
});
|
||||
}
|
||||
|
||||
g.reset();
|
||||
Bangle.setUI();
|
||||
mainMenu();
|
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"name":"EU emergency",
|
||||
"number":"112"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,286 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css">
|
||||
<script type="module">
|
||||
import vcf from 'https://cdn.jsdelivr.net/npm/vcf@2.1.1/+esm'
|
||||
window.vcf = vcf;
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
html, body { height: 100% }
|
||||
.flex-col { display:flex; flex-direction:column; height:100% }
|
||||
#map { width:100%; height:100% }
|
||||
#tab-list { width:100%; height:100% }
|
||||
|
||||
/* https://stackoverflow.com/a/58686215 */
|
||||
.arrow-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.arrow-icon > div {
|
||||
margin-left: -1px;
|
||||
margin-top: -3px;
|
||||
transform-origin: center center;
|
||||
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Contacts v.2</h1>
|
||||
<div class="flex-col">
|
||||
<div id="statusarea">
|
||||
<button id="download" class="btn btn-error">Reload</button> <button id="upload" class="btn btn-primary">Upload</button>
|
||||
<span id="status"></span>
|
||||
<span id="routestatus"></span>
|
||||
</div>
|
||||
<div style="flex: 1">
|
||||
<div id="tab-list">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Number</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="contacts">
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<h4>Add a new contact</h4>
|
||||
<form id="add_contact_form">
|
||||
<div class="columns">
|
||||
<div class="column col-3 col-xs-8">
|
||||
<input class="form-input input-sm" type="text" id="add_contact_name" placeholder="Name">
|
||||
</div>
|
||||
<div class="column col-3 col-xs-8">
|
||||
<input class="form-input input-sm" value="123456789" type="text" id="add_number" placeholder="Number">
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column col-3 col-xs-8">
|
||||
<button id="add_contact_button" class="btn btn-primary btn-sm">Add Contact</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="divider"></div>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-5 col-xs-12">
|
||||
<label class="form-label" for="fileinput">Add from vCard file</label>
|
||||
</div>
|
||||
<div class="col-7 col-xs-12">
|
||||
<input id="fileinput" class="form-input" type="file" onchange="readFile(this)" accept=".vcf" multiple/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
|
||||
<script>
|
||||
var contacts = [];
|
||||
|
||||
// ==========================================================================
|
||||
/*** status ***/
|
||||
|
||||
function clean() {
|
||||
$('#status').html('<i class="icon icon-check"></i> No pending changes.');
|
||||
}
|
||||
|
||||
function dirty() {
|
||||
$('#status').html('<b><i class="icon icon-edit"></i> Changes have not been sent to the watch.</b>');
|
||||
}
|
||||
|
||||
/*** contacts ***/
|
||||
|
||||
function deleteContact(arr, i) {
|
||||
arr.splice(i, 1);
|
||||
renderAllContacts();
|
||||
dirty();
|
||||
}
|
||||
|
||||
function renameContact(arr, i) {
|
||||
var name = prompt("Enter new name for the contact:", arr[i].name);
|
||||
if (name == null || name == "" || name == arr[i].name)
|
||||
return;
|
||||
arr[i].name = name;
|
||||
renderAllContacts();
|
||||
dirty();
|
||||
}
|
||||
|
||||
/*** util ***/
|
||||
|
||||
// https://stackoverflow.com/a/22706073
|
||||
function escapeHTML(str){
|
||||
return new Option(str).innerHTML;
|
||||
}
|
||||
|
||||
/*** Bangle.js ***/
|
||||
|
||||
function gotStored(pts) {
|
||||
contacts = pts;
|
||||
renderAllContacts();
|
||||
}
|
||||
|
||||
// ========================================================================== LIST
|
||||
|
||||
var $name = document.getElementById('add_contact_name')
|
||||
var $form = document.getElementById('add_contact_form')
|
||||
var $button = document.getElementById('add_contact_button')
|
||||
var $number = document.getElementById('add_number')
|
||||
var $list = document.getElementById('contacts')
|
||||
|
||||
function compare(a, b){
|
||||
var x = a.name.toLowerCase();
|
||||
var y = b.name.toLowerCase();
|
||||
if (x=="none") {return -1};
|
||||
if (y=="none") {return 1};
|
||||
if (x < y) {return -1;}
|
||||
if (x > y) {return 1;}
|
||||
return 0;
|
||||
}
|
||||
|
||||
$button.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
var name = $name.value.trim()
|
||||
if(!name) return;
|
||||
var number = $number.value.trim();
|
||||
|
||||
contacts.push({
|
||||
name, number,
|
||||
});
|
||||
|
||||
contacts.sort(compare);
|
||||
|
||||
renderAllContacts()
|
||||
$name.value = ''
|
||||
$number.value = (0);
|
||||
|
||||
dirty();
|
||||
});
|
||||
|
||||
function removeContact(index){
|
||||
$name.value = contacts[index].name
|
||||
$number.value = contacts[index].number
|
||||
contacts = contacts.filter((p,i) => i!==index)
|
||||
renderAllContacts()
|
||||
}
|
||||
|
||||
function renderContactsList(){
|
||||
$list.innerHTML = ''
|
||||
contacts.forEach((contact,index) => {
|
||||
var $contact = document.createElement('tr')
|
||||
if(contact.number==undefined){
|
||||
$contact.innerHTML = `<td>${contact.name}</td><td>(no number)</td>`;
|
||||
} else {
|
||||
$contact.innerHTML = `<td>${contact.name}</td><td><a href="tel:${contact.number}">${contact.number}</a></td>`;
|
||||
}
|
||||
let buttons = `<button class="btn btn-action btn-error" onclick="removeContact(${index})"><i class="icon icon-delete"></i></button>`;
|
||||
buttons += `<button class="btn btn-action" onclick="exportVcard(${index})" title="Export vCard"><i class="icon icon-download"></i></button>`;
|
||||
$contact.innerHTML += `<td>${buttons}</td>`;
|
||||
$list.appendChild($contact)
|
||||
})
|
||||
$name.focus()
|
||||
}
|
||||
|
||||
function renderContacts() {
|
||||
renderContactsList();
|
||||
}
|
||||
|
||||
function renderAllContacts() {
|
||||
renderContactsList();
|
||||
}
|
||||
|
||||
function readFile(input) {
|
||||
for(let i=0; i<input.files.length; i++) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", () => {
|
||||
let vcards;
|
||||
try {
|
||||
vcards = vcf.parse(reader.result);
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
return;
|
||||
}
|
||||
vcards.forEach(vcard => {
|
||||
const name = vcard.get('fn')?.valueOf() || vcard.get('n')?.valueOf();
|
||||
const tels = Array.isArray(vcard.get('tel')) ? vcard.get('tel') : [vcard.get('tel')];
|
||||
tels.forEach(tel => {
|
||||
if (tel) {
|
||||
const number = tel.valueOf();
|
||||
contacts.push({name: name, number: number});
|
||||
}
|
||||
});
|
||||
});
|
||||
renderAllContacts();
|
||||
dirty();
|
||||
}, false);
|
||||
reader.readAsText(input.files[i], "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
function exportVcard(index){
|
||||
const vCard = new vcf();
|
||||
vCard.set('n', contacts[index].name);
|
||||
vCard.set('tel', contacts[index].number);
|
||||
Util.saveFile(contacts[index].name+".vcf", "text/vcard", vCard.toString());
|
||||
}
|
||||
|
||||
// ========================================================================== UPLOAD/DOWNLOAD
|
||||
|
||||
function downloadJSONfile(fileid, callback) {
|
||||
// TODO: use interface.js-provided stuff?
|
||||
Puck.write(`\x10(function() {
|
||||
var pts = require("Storage").readJSON("${fileid}")||[{name:"NONE"}];
|
||||
Bluetooth.print(JSON.stringify(pts));
|
||||
})()\n`, contents => {
|
||||
if (contents=='[{name:"NONE"}]') contents="[]";
|
||||
var storedpts = JSON.parse(contents);
|
||||
callback(storedpts);
|
||||
clean();
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFile(fileid, contents) {
|
||||
// TODO: use interface.js-provided stuff?
|
||||
Puck.write(`\x10(function() {
|
||||
require("Storage").write("${fileid}",'${contents}');
|
||||
Bluetooth.print("OK");
|
||||
})()\n`, ret => {
|
||||
console.log("uploadFile", ret);
|
||||
if (ret == "OK")
|
||||
clean();
|
||||
});
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
downloadJSONfile("contacts.json", gotStored);
|
||||
}
|
||||
|
||||
$('#download').on('click', function() {
|
||||
downloadJSONfile("contacts.json", gotStored);
|
||||
});
|
||||
|
||||
$('#upload').click(function() {
|
||||
var data = JSON.stringify(contacts);
|
||||
uploadFile("contacts.json",data);
|
||||
});
|
||||
|
||||
// ========================================================================== FINALLY...
|
||||
clean();
|
||||
renderAllContacts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,19 @@
|
|||
{ "id": "contacts",
|
||||
"name": "Contacts",
|
||||
"version":"0.01",
|
||||
"description": "Provides means of storing user contacts, viewing/editing them on device and from the App loader",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"dependencies": {"textinput":"type"},
|
||||
"storage": [
|
||||
{"name":"contacts.app.js","url":"contacts.app.js"},
|
||||
{"name":"contacts.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"contacts.json","url":"contacts.json"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,7 @@
|
|||
A clock with a daylight world map
|
||||
|
||||
The function for the daylight graph is a crude approximation for an equirectangular projection of a circle on a sphere.
|
||||
|
||||
You can change the longitudinal map offset by swiping the map sideways. For saving the changes to the file dwm-clock.json press the top left quarter of the screen. To discard changes press the top right quarter.
|
||||
|
||||
If you are interested in changing the vector font to another one, please do.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4n/AoPg/8fAYM5sGt4FakFjsFKgHGv9rsEpoHOkGl0ExsHvj0AkQAugHyAYMv/4ECkX/BwfwAogACgQDCl8PBQnwBQZ3BC4opDAAg0BGIP/+CVCAoPykAiBiIABjOZBwIMB/8PUYQAJ4AYCiuZzLLQCwUYgEEgGZuAXQ5bEBqMAgeTChaHCiMc2cRmFTBQMJ0AXMl8Rj0QqALEyAwL+QXBGAIKFhJHOjcMglz4EMjew4IwJWILVBiMWPQNOstABgMBsxFJkAvCiNsmc+AgPqSIJIIe4KPEBAMG9FstQFCJA/y/4WBC4PNBo0IhQXICwUijy3FglggtKjIXMBYsKwAaBjPwRIICBIgYXJgGFgHBiMvZ4QtCgQXLhXOSgOfQ4TTCAoR3CuAYGhgXBySiBCgIvB+AXEisA8AXEgIXBiUvkUvLwP/AoIXDAATtCAAPhC4QOBL4Mvh//SAYXHhwHCjIPC+Hwh8CkCTCC4cWFwoXEJAIUCPgPyC4/RBAeSLIZiCAYIvIgO0C45LCI5WLR4RIEAAMgIwLaBC4cbCwMIigXI/4lCbQIXDiLVCjdhMA7NEO4kRmEACogXF+AXDgQOEiEGCwrBFC5MeXoYXHl7uBDQJHFABKoFYQQXOYIzXFABUSC4rXFABTZFC6JIHC6EZDAwXPJI4PIilBAgdEAAOZz//UwIXCj3r3YABxGwcgVLwDqDgF5zOQgEPj3QBYgANhOZzMwCyQA/ABYA="))
|
|
@ -0,0 +1,224 @@
|
|||
// daylight world map clock
|
||||
// equirectangular projected map and approximated daylight graph
|
||||
|
||||
// load font for timezone, weekday and day in month
|
||||
require("FontDennis8").add(Graphics);
|
||||
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
|
||||
const TZOFFSET = new Date().getTimezoneOffset();
|
||||
|
||||
const UTCSTRING = ((TZOFFSET > 0 ? "-" : "+")
|
||||
+ ("0" + Math.floor(Math.abs(TZOFFSET) / 60)).slice(-2))
|
||||
+ (TZOFFSET % 60 ? Math.abs(TZOFFSET) % 60 : "");
|
||||
|
||||
function getMap() {
|
||||
return {
|
||||
width: 176, height: 88, bpp: 1,
|
||||
transparent: 1,
|
||||
buffer: require("heatshrink").decompress(atob("/4A/AA0Av+Ag4UQwBhDn//1//8///AUI3MAhAUBgIQBh4LC/kfCg34rmAngVD/1/CYICBA4IAF8EOwF/+AVCAAXj//AA4PjDQIVDgkQj/4gBtEx+EGgXwCoJ8Bv+8geQgIVE4P/553Egf/nwFCgUE4H8gBqB/0AhLxHggFE+E8gJoBDIIAI5wFE4F8h/4v5FBABA2BAAUf7n+VYXgoAVNn/Dv+fCoPACo8MEQPHHAUf4DuB//58FgCgsHeoWfMgUDConw4AVFh/wXIRDDwBWC8jfBFY3xaAa5DYYXkKw8D+YVDHAcXAwKuIgIUDSIIJCsYVKeAIVHj5fGNogVHgN/AwPyEgPhCokZCo40D8E0wcwTYhsECoY0D8H2hEACocBCoqnCKwQVB/nICokJ+4VL/RGBQQkdw4VESQTwCDgIVBNgkeEQaSEQQReC4QrEhwUECoUECooAFVwoABgF+CoY+DAYZAFAAOgv4VGoFgCpXwGIoABkEHDQUvCo9zD4YVE4EIgIUGCoNnZwYVCiEP8E8hYVH/kHII0Qj/wvkP94WH4IVGhE/MQMH54VH+IVGKYIJBgfnCo/98IVFcYP5/9HMYbdGn7FFv/4/9vCpH/4DmC4AVCD4P/n4VKUoXgCwQ2Cz42CCpX//BtCCoMeCpJTBZgcAgYFCjElCpA7BEIQVBZoeYp4sICoIQCIIJzC/+Mp+DCpJSC/kAj4KC5/f4GfK5AVIeYPgNpIVEIIf/6f/v6ZHPwYVG//7V5BtDCoMOEof+jYVH8AVFhgLD/EZCo6UBCokYBYa2BCp04G4oVJNAX+gF4XYqDHCoKqCCoIrDAoL9DCowfCB4N9CorMDCooPEfowVMB4IVPeAQABwIVPeAQABw4LEg/ANo/wTAQAI8E//YVS+F//IIGGg4AFCo7OHAAf+v/jCowqM//HAwvhCpuPOwwVNAAwrOAA3xCqhtOAH4AfW4wAN/0/A4sP//AgFygYVH/V/AwlwgE8gAACDYIAF9ArC+uACAUgCocAHIn8k/gj4FBCgYAGBoXwgEYDof+ChMAJ4PmAwcBDgIUKgANBJIkZ/0cCpYrBIAIADzkwChQ5B/tgBAh7FNpANMAGg="))
|
||||
};
|
||||
}
|
||||
|
||||
const YOFFSET = H - getMap().height;
|
||||
|
||||
// map offset in degree
|
||||
// -180 to 180 / default: 0
|
||||
function getLongitudeOffset() {
|
||||
return require("Storage").readJSON("dwm-clock.json", 1) || {"lon": 0};
|
||||
}
|
||||
|
||||
function drawMap() {
|
||||
g.setBgColor(0, 0, 0);
|
||||
|
||||
// does not flip on it's own, but there is a draw function after that does
|
||||
g.drawImages([{
|
||||
x: -lonOffset * W / 360,
|
||||
y: YOFFSET,
|
||||
image: getMap(),
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
center: false,
|
||||
repeat: true,
|
||||
nobounds: false
|
||||
}], {
|
||||
x: 0,
|
||||
y: YOFFSET,
|
||||
width: getMap().width,
|
||||
height: getMap().height
|
||||
});
|
||||
}
|
||||
|
||||
function drawDaylightMap() {
|
||||
// number of xy points, < 40 looks very skewed around solstice
|
||||
const STEPS = 40;
|
||||
const YFACTOR = getMap().height / 2;
|
||||
const YOFF = H / 2 + YFACTOR;
|
||||
var graph = [];
|
||||
|
||||
// progress of day, float 0 to 1
|
||||
var dayOffset = (now.getHours() + (now.getMinutes() + TZOFFSET) / 60) / 24;
|
||||
|
||||
// sun position modifier
|
||||
var sunPosMod;
|
||||
|
||||
var solarNoon = require("suncalc").getTimes(now, 0, 0, 0).solarNoon;
|
||||
|
||||
var altitude = require("suncalc").getPosition(solarNoon, 0, 0).altitude;
|
||||
|
||||
// this is trial and error. no thought went into this
|
||||
sunPosMod = Math.pow(altitude - 0.08, 8);
|
||||
|
||||
// switch sign on equinox
|
||||
// this is an approximation
|
||||
if (require("suncalc").getPosition(solarNoon, 0, 0).azimuth < -1) {
|
||||
sunPosMod = -sunPosMod;
|
||||
}
|
||||
|
||||
for (var x = 0; x < (STEPS + 1) / STEPS; x += 1 / STEPS) {
|
||||
// this is an approximation instead of projecting a circle onto a sphere
|
||||
// y = arctan(sin(x) * n)
|
||||
var y = Math.atan(Math.sin(2 * Math.PI * x + dayOffset * 2 * Math.PI
|
||||
// user defined map offset fixed offset
|
||||
// v v
|
||||
+ 2 * Math.PI * lonOffset / 360 - Math.PI / 2) * sunPosMod)
|
||||
* (2 / Math.PI);
|
||||
// ^
|
||||
// factor keeps y <= 1
|
||||
|
||||
graph.push(x * W, y * YFACTOR + YOFF);
|
||||
}
|
||||
|
||||
// day area, yellow
|
||||
g.setColor(0.8, 0.8, 0.3);
|
||||
g.fillRect(0, YOFFSET, W, H);
|
||||
|
||||
// night area, blue
|
||||
g.setColor(0, 0, 0.5);
|
||||
// switch on equinox
|
||||
if (sunPosMod < 0) {
|
||||
g.fillPoly([0, H - 1].concat(graph, W - 1, H - 1));
|
||||
} else {
|
||||
g.fillPoly([0, YOFFSET].concat(graph, W, YOFFSET));
|
||||
}
|
||||
|
||||
drawMap();
|
||||
|
||||
// day-night line, white
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawPoly(graph, false);
|
||||
}
|
||||
|
||||
function drawClock() {
|
||||
// clock area
|
||||
g.clearRect(0, YOFFSET, W, 24);
|
||||
|
||||
// clock text
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(0, -1);
|
||||
g.setFont("Vector", 58);
|
||||
// with the vector font this leaves 26px above the text
|
||||
g.drawString(require("locale").time(now, 1), W / 2, 24 - 2);
|
||||
|
||||
|
||||
// timezone text
|
||||
g.setFontAlign(-1, 1);
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString("UTC" + UTCSTRING, 3, YOFFSET);
|
||||
|
||||
|
||||
// day text
|
||||
g.setFontAlign(1, 1);
|
||||
g.setFont("Dennis8", 2);
|
||||
g.drawString(require("locale").dow(now, 1) + " " + now.getDate(),
|
||||
W - 1, YOFFSET);
|
||||
}
|
||||
|
||||
function renderScreen() {
|
||||
now = new Date();
|
||||
|
||||
drawClock();
|
||||
drawDaylightMap();
|
||||
}
|
||||
|
||||
function renderAndQueue() {
|
||||
timeoutID = setTimeout(renderAndQueue, 60000 - (Date.now() % 60000));
|
||||
renderScreen();
|
||||
}
|
||||
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
g.setBgColor(0, 0, 0);
|
||||
|
||||
var now = new Date();
|
||||
|
||||
// map offsets
|
||||
var defLonOffset = getLongitudeOffset().lon;
|
||||
var lonOffset = defLonOffset;
|
||||
|
||||
var timeoutID;
|
||||
var timeoutIDTouch;
|
||||
|
||||
Bangle.on('drag', function(touch) {
|
||||
|
||||
if (timeoutIDTouch) {
|
||||
clearTimeout(timeoutIDTouch);
|
||||
}
|
||||
|
||||
// return after not touching for 5 seconds
|
||||
timeoutIDTouch = setTimeout(renderAndQueue, 5 * 1000);
|
||||
|
||||
// touch map
|
||||
if (touch.y >= YOFFSET) {
|
||||
lonOffset -= touch.dx * 360 / W;
|
||||
|
||||
// wrap map offset
|
||||
if (lonOffset < -180) {
|
||||
lonOffset += 360;
|
||||
} else if (lonOffset >= 180) {
|
||||
lonOffset -= 360;
|
||||
}
|
||||
|
||||
// snap to 0° longitude
|
||||
if (lonOffset > -5 && lonOffset < 5) {
|
||||
lonOffset = 0;
|
||||
}
|
||||
|
||||
lonOffset = Math.round(lonOffset);
|
||||
|
||||
// clock area
|
||||
g.clearRect(0, YOFFSET, W, 24);
|
||||
|
||||
// text
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(0, -1);
|
||||
g.setFont("Dennis8", 2);
|
||||
// could not get ° (degree sign) to render
|
||||
g.drawString("select lon offset\n< tap: save\nreset: tap >\n"
|
||||
+ lonOffset + " degree", W / 2, 24);
|
||||
|
||||
drawDaylightMap();
|
||||
|
||||
// touch clock, left side, save offset
|
||||
} else if (touch.x < W / 2) {
|
||||
if (defLonOffset != lonOffset) {
|
||||
require("Storage").writeJSON("dwm-clock.json", {"lon": lonOffset});
|
||||
defLonOffset = lonOffset;
|
||||
}
|
||||
|
||||
renderScreen();
|
||||
|
||||
// touch clock, right side, reset offset
|
||||
} else {
|
||||
lonOffset = defLonOffset;
|
||||
renderScreen();
|
||||
}
|
||||
});
|
||||
|
||||
renderAndQueue();
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"id": "dwm-clock",
|
||||
"name": "Daylight World Map Clock",
|
||||
"shortName": "DWM Clock",
|
||||
"version": "0.01",
|
||||
"description": "A clock with a daylight world map",
|
||||
"readme":"README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"dwm-clock.app.js","url":"app.js"},
|
||||
{"name":"dwm-clock.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"dwm-clock.json"}]
|
||||
}
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -128,7 +128,6 @@ LED.set();NRF.sleep();`);
|
|||
posteditor.on("change", editorChanged);
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
if (!hasWarnings()) {
|
||||
var precode = preeditor.getValue();
|
||||
var jscode = jseditor.getValue();
|
||||
var postcode = posteditor.getValue();
|
||||
|
@ -159,7 +158,6 @@ LED.set();NRF.sleep();`);
|
|||
})}]
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
document.getElementById("setdefault").addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -83,11 +83,8 @@
|
|||
|
||||
function onInit() {
|
||||
Util.showModal("Loading...");
|
||||
Util.readStorage("espruinoterm.json", function(j) {
|
||||
Util.readStorageJSON("espruinoterm.json", function(options) {
|
||||
Util.hideModal();
|
||||
try {
|
||||
options = JSON.parse(j);
|
||||
} catch (e) {}
|
||||
if (!Array.isArray(options)) setDefaults();
|
||||
refresh();
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ This allows fast loading of all apps with two conditions:
|
|||
## App history
|
||||
|
||||
* Long press of hardware button clears the app history and loads the clock face
|
||||
* Installing the 'Fast Reset' app allows doing fastloads directly to the clock face by pressing the hardware button for one second. Useful if there are many apps in the history and the user want to access the clock quickly.
|
||||
* Installing the 'Fast Reset' app allows doing fastloads directly to the clock face by pressing the hardware button just a little longer than a click. Useful if there are many apps in the history and the user want to access the clock quickly.
|
||||
|
||||
## Technical infos
|
||||
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Shorten the timeout before executing to 250 ms.
|
||||
0.03: Add inner timeout of 150 ms so user has more time to release the button
|
||||
before clock ui is initialized and adds it's button watch for going to
|
||||
launcher.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{let buzzTimeout;
|
||||
setWatch((e)=>{
|
||||
if (e.state) buzzTimeout = setTimeout(()=>{Bangle.buzz(80,0.40);Bangle.showClock();}, 250);
|
||||
if (e.state) buzzTimeout = setTimeout(()=>{Bangle.buzz(80,0.40);setTimeout(Bangle.showClock,150);}, 250);
|
||||
if (!e.state && buzzTimeout) clearTimeout(buzzTimeout);},
|
||||
BTN,{repeat:true,edge:'both'});}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "fastreset",
|
||||
"name": "Fast Reset",
|
||||
"shortName":"Fast Reset",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Reset the watch by pressing the hardware button just a little bit longer than a click. If 'Fastload Utils' is installed this will typically be done with fastloading. A buzz acts as indicator.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///x3ygHo8H1Jf8AgILLoALVgoLHggLCqAgJioLIqgLDGQsBqtAioOBqoYFqtUAIMVBY9VqwCBDIIAECYILCHowLBrWVBZFlrWWyptGgtq1WqJYI7GrQLCFxGrBYJHEBQNV1Wv9IEBEocFKIOq//qJAIZEAoNq3/+1QMBHoYYBrQLB1J4GitaEYZfGtfvBYJ3HtWr9WlNY0V1Nr1WlC4xIBrWmBZWVrJGFcYILBZY4LBoILIgoNBEILvHDIQ5BBY4IBBYMBMAwLBBA4LPBRMAKAoLRiALWAGw="))
|
|
@ -0,0 +1,59 @@
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showMessage("Loading...");
|
||||
Bangle.setOptions({hrmPollInterval:5});
|
||||
Bangle.setHRMPower(1);
|
||||
|
||||
function drawCounter() {
|
||||
g.reset().clearRect(0,24,175,90);
|
||||
//g.drawRect(0,24,175,90);
|
||||
g.setFontAlign(0,0).setFontVector(60);
|
||||
g.drawString(count, 88, 60);
|
||||
}
|
||||
|
||||
function hadPulse() {
|
||||
count++;
|
||||
drawCounter();
|
||||
g.setColor("#f00").fillCircle(156,156,20);
|
||||
setTimeout(function() {
|
||||
g.setColor(g.theme.bg).fillCircle(156,156,20);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
if (parseFloat(process.env.VERSION.replace("v","0"))<2019) {
|
||||
E.showMessage("You need at least firmware 2v19","Error");
|
||||
} else if (Bangle.hrmRd(0)!=33) { // wrong sensor - probably VC31 from original bangle.js 2
|
||||
E.showMessage("This Bangle.js doesn't have a VC31B HRM sensor","Error");
|
||||
} else {
|
||||
Bangle.setOptions({hrmGreenAdjust:false, hrmWearDetect:false, hrmPushEnv:true});
|
||||
Bangle.hrmWr(0x10, 197&0xF8 | 4); // just SLOT2
|
||||
Bangle.hrmWr(0x16, 0); // force env to be used as fast as possible
|
||||
|
||||
var samples = 0, samplesHi = 0;
|
||||
var count = 0;
|
||||
{
|
||||
let last = 0;
|
||||
Bangle.on('HRM-env',v => {
|
||||
if (v) {
|
||||
if (!last) hadPulse();
|
||||
samplesHi++;
|
||||
}
|
||||
last = v;
|
||||
samples++;
|
||||
});
|
||||
}
|
||||
|
||||
drawCounter();
|
||||
setInterval(function() {
|
||||
g.reset().clearRect(0,90,175,130);
|
||||
g.setFontAlign(0,0).setFont("6x8:2");
|
||||
g.drawString(samples+" sps", 88, 100);
|
||||
if (samplesHi*5 > samples) {
|
||||
g.setBgColor("#f00").setColor("#fff");
|
||||
g.clearRect(0,110,175,130).drawString("TOO LIGHT",88,120);
|
||||
}
|
||||
samples=0;
|
||||
samplesHi=0;
|
||||
Bangle.setLCDPower(1); // force LCD on!
|
||||
}, 1000);
|
||||
}
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,13 @@
|
|||
{ "id": "flashcount",
|
||||
"name": "Flash Counter",
|
||||
"shortName":"FlashCount",
|
||||
"version":"0.01",
|
||||
"description": "Count flashes/pulses of light using the heart rate monitor. Requires a VC31B HRM sensor, which should be in most watches except those produced for the original KickStarter campaign.",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"flashcount.app.js","url":"app.js"},
|
||||
{"name":"flashcount.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: attempt to import
|
|
@ -0,0 +1,8 @@
|
|||
# App Forge
|
||||
|
||||
This should help with your hacks. Sometimes, you want to work on an
|
||||
application, you'd want to use the stable version, but you'd also want
|
||||
to use latest development version.
|
||||
|
||||
Well, this makes it easy. Just save your development version as
|
||||
a.name.js, and you should be able to run it from the menu system.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIhe/AEDgOKAocDwgFDgUEAokKAokTAohDEg0hgEgAoMEoMIoAFCgME4AFCwUCwAFBgoeChEAg8GAoMYEYMECIM4AoMMgFAuEAhv4gkg+EAhPghExAoIACg4FEh4FEj4FEn56Ev/8iAFC///CQUBAoPgQoQFBLYUHAoJbCh4FBFwf//wuD//8Fwf/GoYuNAoUGGggMCGgQeCbIl+Aol8Aol4Aoh2EgFgf5kAA"))
|
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,32 @@
|
|||
// App Forge
|
||||
|
||||
st = require('Storage');
|
||||
|
||||
l = /^a\..*\.js$/;
|
||||
//l = /.*\.js/;
|
||||
l = st.list(l, {sf:false});
|
||||
|
||||
print(l);
|
||||
|
||||
function on_load(x) {
|
||||
print("Loading", x);
|
||||
Bangle.buzz(50, 1); // Won't happen because load() is quicker
|
||||
g.reset().clear()
|
||||
.setFont("Vector", 40)
|
||||
.drawString("Loading", 0, 30)
|
||||
.drawString(x, 0, 80);
|
||||
g.flip();
|
||||
load(x);
|
||||
}
|
||||
|
||||
var menu = {
|
||||
"< Back" : Bangle.load
|
||||
};
|
||||
if (l.length==0) Object.assign(menu, {"No apps":""});
|
||||
else for (let id in l) {
|
||||
let i = id;
|
||||
menu[l[id]]=()=>{ on_load(l[i]); };
|
||||
}
|
||||
|
||||
g.clear();
|
||||
E.showMenu(menu);
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "forge",
|
||||
"name": "App Forge",
|
||||
"version":"0.01",
|
||||
"description": "Easy way to run development versions of your apps",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"tags": "tool",
|
||||
"storage": [
|
||||
{"name":"forge.app.js","url":"forge.app.js"},
|
||||
{"name":"forge.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -12,7 +12,8 @@
|
|||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
|
||||
</div>
|
||||
<ul>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span></p>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span>.
|
||||
The DFU (bootloader) rarely changes, so it does not have to be the same version as your main firmware.</p>
|
||||
</ul>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p id="fw-old-bootloader-msg">If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"tags": "tool,outdoors,gps",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"dependencies" : { "waypoints":"type" },
|
||||
"dependencies" : { "waypoints":"app" },
|
||||
"storage": [
|
||||
{"name":"gpsnav.app.js","url":"app.min.js","supports":["BANGLEJS"]},
|
||||
{"name":"gpsnav.app.js","url":"app_b2.js","supports":["BANGLEJS2"]},
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"tags": "tool,outdoors,gps",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"dependencies" : { "waypoints":"type" },
|
||||
"dependencies" : { "waypoints":"app" },
|
||||
"interface" : "interface.html",
|
||||
"storage": [
|
||||
{"name":"gpstrek.app.js","url":"app.js"},
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Refactor code to store grocery list in separate file
|
||||
0.03: Sort selected items to bottom and enable Widgets
|
||||
0.04: Add settings to edit list on device
|
||||
0.05: Drop app customiser as it is redundant with download interface
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
var filename = 'grocery_list.json';
|
||||
var settings = require("Storage").readJSON(filename,1)|| { products: [] };
|
||||
{
|
||||
const filename = 'grocery_list.json';
|
||||
const settings = require("Storage").readJSON(filename,1)|| { products: [] };
|
||||
let menu;
|
||||
|
||||
function updateSettings() {
|
||||
const updateSettings = function() {
|
||||
require("Storage").writeJSON(filename, settings);
|
||||
Bangle.buzz();
|
||||
}
|
||||
};
|
||||
|
||||
function twoChat(n){
|
||||
const twoChat = function(n) {
|
||||
if(n<10) return '0'+n;
|
||||
return ''+n;
|
||||
}
|
||||
};
|
||||
|
||||
function sortMenu() {
|
||||
const sortMenu = function() {
|
||||
mainMenu.sort((a,b) => {
|
||||
const byValue = a.value-b.value;
|
||||
return byValue !== 0 ? byValue : a.index-b.index;
|
||||
|
@ -20,7 +21,7 @@ function sortMenu() {
|
|||
if (menu) {
|
||||
menu.draw();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mainMenu = settings.products.map((p,i) => ({
|
||||
title: twoChat(p.quantity)+' '+p.name,
|
||||
|
@ -35,9 +36,14 @@ const mainMenu = settings.products.map((p,i) => ({
|
|||
}));
|
||||
sortMenu();
|
||||
|
||||
mainMenu[''] = { 'title': 'Grocery list' };
|
||||
mainMenu[''] = {
|
||||
'title': 'Grocery list',
|
||||
remove: () => {
|
||||
},
|
||||
};
|
||||
mainMenu['< Back'] = ()=>{load();};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
menu = E.showMenu(mainMenu);
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
<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>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">Reset</button> <button id="upload" class="btn btn-primary">Upload</button>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var products = []
|
||||
try{
|
||||
var stored = localStorage.getItem('grocery-product-list')
|
||||
if(stored) products = JSON.parse(stored);
|
||||
}catch(e){}
|
||||
|
||||
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')
|
||||
|
||||
renderProducts()
|
||||
|
||||
$reset.addEventListener('click', reset)
|
||||
|
||||
$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
|
||||
save()
|
||||
})
|
||||
|
||||
function save(){
|
||||
localStorage.setItem('grocery-product-list',JSON.stringify(products));
|
||||
}
|
||||
|
||||
function reset(){
|
||||
products = []
|
||||
save()
|
||||
renderProducts()
|
||||
}
|
||||
|
||||
function removeProduct(index){
|
||||
products = products.filter((p,i) => i!==index)
|
||||
save()
|
||||
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><button class="btn btn-error" onclick="removeProduct(${index})">remove</button></td>`
|
||||
$list.appendChild($product)
|
||||
})
|
||||
|
||||
$name.focus()
|
||||
|
||||
}
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{ name:"grocery_list.json", content: JSON.stringify({products: products}) }
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -76,11 +76,11 @@
|
|||
function getData() {
|
||||
// show loading window
|
||||
Util.showModal("Loading...");
|
||||
Util.readStorage('grocery_list.json', data=>{
|
||||
Util.readStorageJSON('grocery_list.json', data=>{
|
||||
// remove window
|
||||
Util.hideModal();
|
||||
|
||||
settings = JSON.parse(data || "{products: []}");
|
||||
settings = data || {"products": []};
|
||||
products = settings.products;
|
||||
renderProducts();
|
||||
});
|
||||
|
@ -89,7 +89,6 @@
|
|||
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();
|
||||
});
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
{
|
||||
"id": "grocery",
|
||||
"name": "Grocery",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"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,shopping,list",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"custom": "grocery.html",
|
||||
"interface": "interface.html",
|
||||
"allow_emulator": true,
|
||||
"dependencies": {"textinput":"type"},
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
/*LANG*/"Edit List": () => editlist(),
|
||||
/*LANG*/"Add item": () => {
|
||||
settings.products.push({
|
||||
"name":/*LANG*/"New item",
|
||||
"name":/*LANG*/"New",
|
||||
"quantity":1,
|
||||
"ok":false
|
||||
});
|
||||
|
|
|
@ -211,9 +211,9 @@
|
|||
this.editSong = song;
|
||||
},
|
||||
loadSongs: function () {
|
||||
Util.readStorage('guitar_songs.json', (contents) => {
|
||||
Util.readStorageJSON('guitar_songs.json', (contents) => {
|
||||
this.songsState = 'loaded';
|
||||
this.localSongs = JSON.parse(contents) || [];
|
||||
this.localSongs = contents || [];
|
||||
this.watchSongs = JSON.parse(JSON.stringify(this.localSongs));
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Respect Quiet Mode
|
||||
0.03: Fix hour/minute wrapping code for new menu system
|
||||
0.04: Use default Bangle formatter for booleans
|
||||
0.05: Support BangleJS 2
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
Hard Alarm
|
||||
========
|
||||
|
||||
The Hard Alarm will is vibration only alarm clock that will not stop vibrating until you are awake enough to match the random number on the screen.
|
||||
|
||||
To set a hard alarm open the app and in the settings screen:
|
||||
1. Press the top half of the screen add 1 and press the bottom half of the screen to subtract 1.
|
||||
2. Press the center number itself to save your selection
|
||||
3. Exit the app or it may not run the alarm.
|
||||
|
||||
When the alarm goes off:
|
||||
1. Press the top half of the screen add 1 and press the bottom half of the screen to subtract 1.
|
||||
2. Press the side button to confirm your selection and turn off the alarm
|
||||
3. You now have the touchscreen option to Snooze the alarm for 10 more minutes or end the alarm.
|
||||
|
||||
Made by https://dare.fail
|
|
@ -8,7 +8,6 @@ var alarms = require("Storage").readJSON("hardalarm.json",1)||[];
|
|||
msg : "Eat chocolate",
|
||||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||
rp : true, // repeat
|
||||
as : false, // auto snooze
|
||||
}
|
||||
];*/
|
||||
|
||||
|
@ -45,14 +44,12 @@ function editAlarm(alarmIndex) {
|
|||
var mins = 0;
|
||||
var en = true;
|
||||
var repeat = true;
|
||||
var as = false;
|
||||
if (!newAlarm) {
|
||||
var a = alarms[alarmIndex];
|
||||
hrs = 0|a.hr;
|
||||
mins = Math.round((a.hr-hrs)*60);
|
||||
en = a.on;
|
||||
repeat = a.rp;
|
||||
as = a.as;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
|
@ -72,10 +69,6 @@ function editAlarm(alarmIndex) {
|
|||
value: en,
|
||||
onchange: v=>repeat=v
|
||||
},
|
||||
/*LANG*/'Auto snooze': {
|
||||
value: as,
|
||||
onchange: v=>as=v
|
||||
}
|
||||
};
|
||||
function getAlarm() {
|
||||
var hr = hrs+(mins/60);
|
||||
|
@ -86,7 +79,7 @@ function editAlarm(alarmIndex) {
|
|||
// Save alarm
|
||||
return {
|
||||
on : en, hr : hr,
|
||||
last : day, rp : repeat, as: as
|
||||
last : day, rp : repeat
|
||||
};
|
||||
}
|
||||
menu["> Save"] = function() {
|
||||
|
|