diff --git a/README.md b/README.md index ddcf23f259..b9b770b374 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,8 @@ and which gives information about the app for the Launcher. // 'notify' - provides 'notify' library for showing notifications // 'locale' - provides 'locale' library for language-specific date/distance/etc // (a version of 'locale' is included in the firmware) - "tags": "", // comma separated tag list for searching + // 'defaultconfig' - a set of apps that will can be installed and will wipe out all previously installed apps + "tags": "", // comma separated tag list for searching (don't include uppercase or spaces) // common types are: // 'clock' - it's a clock // 'widget' - it is (or provides) a widget diff --git a/apps/UI4swatch/metadata.json b/apps/UI4swatch/metadata.json index bcb0e6c51d..5b4abf4061 100644 --- a/apps/UI4swatch/metadata.json +++ b/apps/UI4swatch/metadata.json @@ -5,7 +5,7 @@ "version": "0.02", "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", "icon": "app.png", - "tags": "Color,input,buttons,touch,UI", + "tags": "color,input,buttons,touch,ui", "supports": ["BANGLEJS"], "readme": "README.md", "screenshots": [{"url":"UI4swatch_icon.png"},{"url":"UI4swatch_s1.png"}], diff --git a/apps/Uke/metadata.json b/apps/Uke/metadata.json index 5a3f17639f..1185c7202c 100644 --- a/apps/Uke/metadata.json +++ b/apps/Uke/metadata.json @@ -4,8 +4,8 @@ "version": "0.04", "description": "Wrist mounted ukulele chords", "icon": "app.png", - "tags": "uke, chords", - "supports" : ["BANGLEJS2"], + "tags": "uke,chords", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"Uke.app.js","url":"app.js"}, diff --git a/apps/bblobface/metadata.json b/apps/bblobface/metadata.json index 6af247c913..8393755b09 100644 --- a/apps/bblobface/metadata.json +++ b/apps/bblobface/metadata.json @@ -6,7 +6,7 @@ "description": "A fully featured watch face with a playable game on the side.", "readme":"README.md", "type": "clock", - "tags": "clock, game", + "tags": "clock,game", "supports" : ["BANGLEJS2"], "storage": [ {"name":"bblobface.app.js","url":"app.js"}, diff --git a/apps/beeptest/metadata.json b/apps/beeptest/metadata.json index a9856bc932..7c25a38488 100644 --- a/apps/beeptest/metadata.json +++ b/apps/beeptest/metadata.json @@ -5,7 +5,7 @@ "version": "0.01", "description": "Aerobic fitness test created by Léger & Lambert", "icon": "beeptest.png", - "tags": "Health", + "tags": "health", "supports": ["BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/bordle/metadata.json b/apps/bordle/metadata.json index f6011f7988..0dbcf09f91 100644 --- a/apps/bordle/metadata.json +++ b/apps/bordle/metadata.json @@ -6,7 +6,7 @@ "description": "Bangle version of a popular word search game", "supports" : ["BANGLEJS2"], "readme": "README.md", - "tags": "game, text", + "tags": "game,text", "storage": [ {"name":"bordle.app.js","url":"bordle.app.js"}, {"name":"wordlencr.txt","url":"wordlencr.txt"}, diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index 191bb51e5b..74b80b73bf 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -32,4 +32,5 @@ clkinfo.addInteractive that would cause ReferenceError. 0.30: Use widget_utils 0.31: Use clock_info module as an app 0.32: Make the border of the clock_info box extend all the way to the right of the screen. -0.33: Fix issue rendering ClockInfos with for fg+bg color set to the same (#2749) \ No newline at end of file +0.33: Fix issue rendering ClockInfos with for fg+bg color set to the same (#2749) +0.34: Support 12-hour time format diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index fdb82df73f..5053dafbbc 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -239,11 +239,9 @@ let drawTime = function() { var y = y1; var date = new Date(); - var hours = String(date.getHours()); - var minutes = date.getMinutes(); - minutes = minutes < 10 ? String("0") + minutes : minutes; - var colon = settings.hideColon ? "" : ":"; - var timeStr = hours + colon + minutes; + var timeStr = locale.time(date, 1); + if (settings.hideColon) + timeStr = timeStr.replace(":", ""); // Set y coordinates correctly y += parseInt((H - y)/2) + 5; diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index d4091c2fe7..de84ba9473 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.33", + "version": "0.34", "description": "A very minimalistic clock.", "readme": "README.md", "icon": "app.png", diff --git a/apps/bwclklite/ChangeLog b/apps/bwclklite/ChangeLog index c68c62edcc..bba66928ba 100644 --- a/apps/bwclklite/ChangeLog +++ b/apps/bwclklite/ChangeLog @@ -35,4 +35,5 @@ clkinfo.addInteractive that would cause ReferenceError. Remove invertion of theme as this doesn'twork very well with fastloading. Do an quick inital fillRect on theclock info area. 0.33: Make the border of the clock_info box extend all the way to the right of the screen. -0.34: Fix issue rendering ClockInfos with for fg+bg color set to the same (#2749) \ No newline at end of file +0.34: Fix issue rendering ClockInfos with for fg+bg color set to the same (#2749) +0.35: Support 12-hour time format diff --git a/apps/bwclklite/app.js b/apps/bwclklite/app.js index 05f61ec58c..794b39aa9d 100644 --- a/apps/bwclklite/app.js +++ b/apps/bwclklite/app.js @@ -199,11 +199,9 @@ let drawTime = function() { let y = y1; let date = new Date(); - let hours = String(date.getHours()); - let minutes = date.getMinutes(); - minutes = minutes < 10 ? String("0") + minutes : minutes; - let colon = settings.hideColon ? "" : ":"; - let timeStr = hours + colon + minutes; + var timeStr = locale.time(date, 1); + if (settings.hideColon) + timeStr = timeStr.replace(":", ""); // Set y coordinates correctly y += parseInt((H - y)/2) + 5; diff --git a/apps/bwclklite/metadata.json b/apps/bwclklite/metadata.json index f8dffdca92..14af7131c6 100644 --- a/apps/bwclklite/metadata.json +++ b/apps/bwclklite/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclklite", "name": "BW Clock Lite", - "version": "0.34", + "version": "0.35", "description": "A very minimalistic clock. This version of BW Clock is quicker at the cost of the custom font.", "readme": "README.md", "icon": "app.png", diff --git a/apps/calclock/metadata.json b/apps/calclock/metadata.json index c339e8ce0e..31dcb88d08 100644 --- a/apps/calclock/metadata.json +++ b/apps/calclock/metadata.json @@ -6,7 +6,7 @@ "description": "Show the current and upcoming events synchronized from Gadgetbridge", "icon": "calclock.png", "type": "clock", - "tags": "clock agenda", + "tags": "clock,agenda", "supports": ["BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog index 9a4f814911..b5a5fbe2f8 100644 --- a/apps/calendar/ChangeLog +++ b/apps/calendar/ChangeLog @@ -19,3 +19,5 @@ Display Widgets in menus 0.17: Load holidays before events so the latter is not overpainted 0.18: Minor code improvements +0.19: Read events synchronized from Gadgetbridge +0.20: Correct start time of all-day events synchronized from Gadgetbridge diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js index e140ff5766..d6eefce391 100644 --- a/apps/calendar/calendar.js +++ b/apps/calendar/calendar.js @@ -60,6 +60,14 @@ const loadEvents = () => { date.setSeconds(time.s); return {date: date, msg: a.msg, type: "e"}; })); + // all events synchronized from Gadgetbridge + events = events.concat((require("Storage").readJSON("android.calendar.json",1) || []).map(a => { + // All-day events always start at 00:00:00 UTC, so we need to "undo" the + // timezone offsetting to make sure that the day is correct. + const offset = a.allDay ? new Date().getTimezoneOffset() * 60 : 0 + const date = new Date((a.timestamp+offset) * 1000); + return {date: date, msg: a.title, type: a.allDay ? "o" : "e"}; + })); }; const loadSettings = () => { @@ -221,8 +229,8 @@ const drawCalendar = function(date) { }, []); let i = 0; g.setFont("8x12", fontSize); - for (y = 0; y < rowN - 1; y++) { - for (x = 0; x < colN; x++) { + for (let y = 0; y < rowN - 1; y++) { + for (let x = 0; x < colN; x++) { i++; const day = days[i]; const curMonth = day < 15 ? month+1 : day < 50 ? month-1 : month; diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json index 468bceabb8..36713a487e 100644 --- a/apps/calendar/metadata.json +++ b/apps/calendar/metadata.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Calendar", - "version": "0.18", + "version": "0.20", "description": "Monthly calendar, displays holidays uploaded from the web interface and scheduled events.", "icon": "calendar.png", "screenshots": [{"url":"screenshot_calendar.png"}], diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json index fb7dfd4014..5ec12e751b 100644 --- a/apps/cassioWatch/metadata.json +++ b/apps/cassioWatch/metadata.json @@ -6,7 +6,7 @@ "icon": "app.png", "version": "0.13", "type": "clock", - "tags": "clock, weather, cassio, retro", + "tags": "clock,weather,cassio,retro", "supports": ["BANGLEJS2"], "allow_emulator": true, "readme": "README.md", diff --git a/apps/chronlog/metadata.json b/apps/chronlog/metadata.json index 8ed618b279..50a9166bf4 100644 --- a/apps/chronlog/metadata.json +++ b/apps/chronlog/metadata.json @@ -3,7 +3,7 @@ "version":"0.01", "description": "Record time active on a task, course, work or anything really.", "icon": "app.png", - "tags": "logging, record, work, tasks", + "tags": "logging,record,work,tasks", "supports" : ["BANGLEJS2"], "readme": "README.md", "screenshots" : [ { "url":"dump.png"}, { "url":"dump1.png" }, { "url":"dump2.png" }, { "url":"dump3.png" }, { "url":"dump4.png" }, { "url":"dump5.png" }, { "url":"dump6.png" } ], diff --git a/apps/clkshortcuts/ChangeLog b/apps/clkshortcuts/ChangeLog new file mode 100644 index 0000000000..759f687774 --- /dev/null +++ b/apps/clkshortcuts/ChangeLog @@ -0,0 +1 @@ +0.01: New app! \ No newline at end of file diff --git a/apps/clkshortcuts/README.md b/apps/clkshortcuts/README.md new file mode 100644 index 0000000000..0d4b72318d --- /dev/null +++ b/apps/clkshortcuts/README.md @@ -0,0 +1,10 @@ +# Shortcuts + +An app that allows you to create custom ClockInfos that act as shortcuts to your favourite apps. + +## Create a Shortcut +After installing the app, you can open the app to add and manage existing shortcuts. If no shortcuts exist, the app will display a ``[+] Shortcuts`` button as a ClockInfo on your Clock (see screenshots), which when clicked will quickly launch into the app so you can add new shortcuts. + +When adding a shortcut, first select the app you wish to use, then select the icon you wish to register to the shortcut. If a keyboard is installed, you'll get the option to rename the shortcut before saving. + +Once created, your shortcut will appear on any Clocks with ClockInfo support, and tapping the shortcut will launch the chosen app \ No newline at end of file diff --git a/apps/clkshortcuts/add_shortcuts_screenshot.png b/apps/clkshortcuts/add_shortcuts_screenshot.png new file mode 100644 index 0000000000..ea7c6273b9 Binary files /dev/null and b/apps/clkshortcuts/add_shortcuts_screenshot.png differ diff --git a/apps/clkshortcuts/app-icon.js b/apps/clkshortcuts/app-icon.js new file mode 100644 index 0000000000..99f51a2e80 --- /dev/null +++ b/apps/clkshortcuts/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UB/4ACBIM889VAHmqAAwKCrQLH0oLCuBoFhoLCtJ1HtILBtYLH9ILBtALHlILQlRFCwALSnWwgECBY8O1gLJgeoBaojLHZfqAQILIFwMDBY8CFwMO2QLGhwuBlWuBY0K4ED1aDHBYJUBlQLGnRUChQLIKgJHHBYJUBZY8KgZUBBZHCKgILHh2CBZMC0fABZC+CBZBSBC5JUB14MDcY2q1YLIDAILGtY4EAAXpBYNpBY9pBYNauAKFhulBYWqAAwLCqoLHBQQA6A=")) \ No newline at end of file diff --git a/apps/clkshortcuts/app.js b/apps/clkshortcuts/app.js new file mode 100644 index 0000000000..197ca70cff --- /dev/null +++ b/apps/clkshortcuts/app.js @@ -0,0 +1,224 @@ +var storage = require("Storage"); +var keyboard = "textinput"; +try { + keyboard = require(keyboard); +} catch (e) { + keyboard = null; +} + +var icons = [ + {name:"agenda", img :"GBiBAAAAAAA8AAB+AA/n8B/n+BgAGBgAGBn/mBn/mBgAGBgAGBn/mBn/+BgD/BgHDhn+Bhn8MxgMIxgMIx/8Ew/+BgAHDgAD/AAA8A=="}, + {name:"alarm", img:"GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAOBwAcA4A4YcAwYMBgYGBgYGBgYGBgYGBgeGBgHGAwBMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="}, + {name:"mail", img:"GBiBAAAAAAAAAAAAAAAAAB//+D///DAADDgAHDwAPDcA7DPDzDDnDDA8DDAYDDAADDAADDAADDAADD///B//+AAAAAAAAAAAAAAAAA=="}, + {name:"android", img: "GBiBAAAAAAEAgAD/AAD/AAHDgAGBgAMkwAMAwAP/wBv/2BsA2BsA2BsA2BsA2BsA2BsA2Bv/2AP/wADnAADnAADnAADnAADnAAAAAA=="}, + {name:"add", img:"GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgYGBgYGBgYGBgYGBn/mBn/mBgYGBgYGBgYGBgYGBgAGBgAGB//+A//8AAAAAAAAAAAAA=="}, + {name:"bangle", img:"GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA=="}, + {name:"bike", img:"GBiBAAAAAAAAAAAAAAAD+AAD/AADjAABjAfhnAfjyAMDwAcGwB4O+H8M/GGZ5sD7c8fzM8fjM8DDA2GBhn8B/h4AeAAAAAAAAAAAAA=="}, + {name:"map", img:"GBiBAAAAAAAAAAAAAADgGAf8+B//+BjHGBjDGBjDGBjDGBjDGBjDGBjDGBjDGBjDGBjDGBjDGBjjGB//+B8/4BgHAAAAAAAAAAAAAA=="}, + {name:"play", img:"GBiBAAAAAAAAAAAAAA//8B//+BgAGBjAGBjwGBj8GBjeGBjHmBjB2BjB2BjHmBjeGBj8GBjwGBjAGBgAGB//+A//8AAAAAAAAAAAAA=="}, + {name:"fast forward", img:"GBiBAAAAAAAAAAAAAH///v///8AAA8YYA8eeA8f/g8b7w8Y488YYO8YYO8Y488b7w8f/g8eeA8YYA8AAA////3///gAAAAAAAAAAAA=="}, + {name:"rewind", img:"GBiBAAAAAAAAAAAAAH///v///8AAA8AYY8B548H/48PfY88cY9wYY9wYY88cY8PfY8H/48B548AYY8AAA////3///gAAAAAAAAAAAA=="}, + {name:"timer", img:"GBiBAAAAAAB+AAB+AAAAMAB+OAH/nAOByAcA4A4YcAwYMBgYGBgYGBgYGBgYGBgAGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="}, + {name:"connected", img:"GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBngGBn4GBgcGBgOGBnHGBnzGBgxmBgZmBmZmBmZmBgAGBgAGB//+A//8AAAAAAAAAAAAA=="}, + {name:"lock", img:"GBiBAAAAAAA8AAD/AAHDgAGBgAMAwAMAwAMAwAf/4A//8AwAMAwAMAwAMAwYMAw8MAw8MAwYMAwAMAwAMAwAMA//8Af/4AAAAAAAAA=="}, + {name:"battery", img:"GBiBAAAAAAAAAAB+AAB+AAHngAPnwAMAwAMAwAMIwAMIwAMYwAM4wAM+wAN8wAMcwAMYwAMQwAMQwAMAwAMAwAP/wAH/gAAAAAAAAA=="}, + {name:"game", img:"GBiBAAAAAAAAAAAAAAA8AAB+AABmAABmAAB+AAA8AAAYAAAYAAAYAAMYAA//8B//+BgAGBgAGBgAGBgAGB//+A//8AAAAAAAAAAAAA=="}, + {name:"dice", img:"GBiBAAAAAB//8D//+HAAPGMDHmeHnmeHnmMDHmAAHmMDHmeHnmeHnmMDHmAAHmMDHmeHnmeHnmMDHnAAPn///j///h///g///AAAAA=="}, + {name:"gear", img:"GBiBAAAAAAAAAAA8AAB+AABmAA3nsA/D8B8A+Dg8HBx+OAznMAzDMAzDMAznMBx+ODg8HB8A+A/D8A3nsABmAAB+AAA8AAAAAAAAAA=="}, + {name:"wrench", img:"GBiBAAAAAAAAAAAAAAAHgAAfwAA7gAAzEABjOABj+ABh+ABgGADgMAHAcAOP4AcfgA44AB9wADHgADHAADGAAB8AAA4AAAAAAAAAAA=="}, + {name:"calendar", img:"FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"}, + {name:"power", img:"GBiBAAAAAAAAAAB+AAH/gAeBwA4YcAwYMBjbGBnbmDGZjDMYzDMYzDMAzDMAzDGBjBnDmBj/GAw8MA4AcAeB4AH/gAB+AAAAAAAAAA=="}, + {name:"terminal", img:"GBiBAAAAAAAAAAAAAA//8B//+B//+B//+B//+BgAGBgAGBgAGBmAGBjAGBhgGBhgGBjAGBmPmBgAGBgAGB//+A//8AAAAAAAAAAAAA=="}, + {name:"camera", img:"GBiBAAAAAAAAAAD/AAH/gAMAwD8A/H8A/mA8BmD/BmHDhmGBhmMAxmMAxmMAxmMAxmGBhmHDhmD/BmA8BmAABn///j///AAAAAAAAA=="}, + {name:"phone", img:"GBiBAAAAAAAAAAOAAA/AABzgADBgADBgADBgABjgABjAABzAAAxgAA5wAAc58AMf+AGHHADgDABwDAA8GAAfGAAH8AAA4AAAAAAAAA=="}, + {name:"two prong plug", img:"GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA=="}, + {name:"steps", img:"GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA=="}, + {name:"graph", img:"GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA=="}, + {name:"hills", img:"GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA=="}, + {name:"sun", img:"GBiBAAAYAAAYAAAYAAgAEBwAOAx+MAD/AAHDgAMAwAcA4AYAYOYAZ+YAZwYAYAcA4AMAwAHDgAD/AAx+MBwAOAgAEAAYAAAYAAAYAA=="}, + {name:"home", img:"GBiBAAAAAAAAAAAAAAH/gAP/wAdg4A5wYA44MBwf+DgP/BgAGBgAGBgAGBnnmBnnmBnnmBnnmBngGBngGB//+B//+AAAAAAAAAAAAA=="}, + {name:"bell", img:"GBiBAAAAAAAAAAAfgAB/2ADw+AHAMAOAGAcAGD4ADHgADDgADBwADA4AHAcAGAOAOAHAcAPg4ANxwAM5gAP/AAHvAAAHAAACAAAAAA=="}, + {name:"bin", img:"GBiBAAAAAAAAAAB+AB//+B//+AwAMAwAMAxmMAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYANmwAMAwAMAwAP/wAH/gAAAAAAAAA=="}, +]; + +let storedApps; +var showMainMenu = () => { + storedApps = storage.readJSON("clkshortcuts.json", 1) || {}; + + var mainMenu = { + "": { + title: "Shortcuts", + }, + "< Back": () => { + load(); + }, + "New": () => { + // Select the app + getSelectedApp().then((app) => { + getSelectedIcon().then((icon) => { + promptForRename(app.name).then((name) => { + E.showMessage("Saving..."); + storedApps[app.src] = { + name: name, src: app.src, icon: icon + }; + storage.writeJSON("clkshortcuts.json", storedApps); + showMainMenu(); + }).catch(() => { + E.showMessage("Saving..."); + storedApps[app.src] = { + name: app.name, src: app.src, icon: icon + }; + storage.writeJSON("clkshortcuts.json", storedApps); + showMainMenu(); + } ); + }).catch(() => {showMainMenu();}); + }).catch(() => {showMainMenu();}); + }, + }; + getStoredAppsArray(storedApps).forEach((app) => { + mainMenu[app.name] = { + onchange: () => { + showEditMenu(app).then((dirty) => { + if (dirty) { + E.showMessage("Saving..."); + storage.writeJSON("clkshortcuts.json", storedApps); + } + showMainMenu(); + }); + }, + format: v=>"\0" + atob(app.icon) + }; + }); + E.showMenu(mainMenu); +}; + +var showEditMenu = (app) => { + return new Promise((resolve, reject) => { + var editMenu = { + "": { + title: "Edit " + app.name, + }, + "< Back": () => { + resolve(false); + }, + "Name":{ + onchange: () => { + promptForRename(app.name).then((name) => { + storedApps[app.src].name = name; + resolve(true); + }).catch(); + }, + format: v=>app.name.substring(0, 7) + }, + "Icon": { + onchange: () => { + getSelectedIcon().then((icon) => { + storedApps[app.src].icon = icon; + resolve(true); + }).catch(() => resolve(false)); + }, + format: v=>"\0" + atob(app.icon) + }, + "Delete": { + onchange: () => { + delete storedApps[app.src] + resolve(true); + }, + format: v=>"\0" + atob("GBiBAAAAAAAAAAB+AB//+B//+AwAMAwAMAxmMAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYANmwAMAwAMAwAP/wAH/gAAAAAAAAA==") + } + }; + E.showMenu(editMenu); + }); +}; + +var promptForRename = (name) => { + return new Promise((resolve, reject) => { + if (!keyboard) { reject("No textinput is available"); } + else { + return require("textinput").input({text:name}).then((result) => resolve(result)); + } + }); +}; + +var getStoredAppsArray = (apps) => { + var appList = Object.keys(apps); + var storedAppArray = []; + for (var i = 0; i < appList.length; i++) { + var app = "" + appList[i]; + storedAppArray.push( + apps[app] + ); + } + return storedAppArray; +}; + +var getSelectedIcon = () => { + return new Promise((resolve, reject) => { + var iconMenu = { + "": { + title: "Select Icon", + }, + "< Back": () => { + reject("The user cancelled the operation"); + }, + }; + + icons.forEach((icon) => { + iconMenu["\0" + atob(icon.img) + " " + icon.name] = () => { + resolve(icon.img); + }; + }); + + E.showMenu(iconMenu); + }); +}; + +var getAppList = () => { + var appList = storage + .list(/\.info$/) + .map((appInfoFileName) => { + var appInfo = storage.readJSON(appInfoFileName, 1); + return ( + appInfo && { + name: appInfo.name, + sortorder: appInfo.sortorder, + src: appInfo.src, + } + ); + }) + .filter((app) => app && !!app.src); + appList.sort((a, b) => { + var n = (0 | a.sortorder) - (0 | b.sortorder); + if (n) return n; + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + + return appList; +}; + +var getSelectedApp = () => { + return new Promise((resolve, reject) => { + E.showMessage("Loading apps..."); + var selectAppMenu = { + "": { + title: "Select App", + }, + "< Back": () => { + reject("The user cancelled the operation"); + }, + }; + + var appList = getAppList(); + appList.forEach((app) => { + selectAppMenu[app.name] = () => { + resolve(app); + }; + }); + + E.showMenu(selectAppMenu); + }); +}; + +showMainMenu(); \ No newline at end of file diff --git a/apps/clkshortcuts/app.png b/apps/clkshortcuts/app.png new file mode 100644 index 0000000000..66e28fde79 Binary files /dev/null and b/apps/clkshortcuts/app.png differ diff --git a/apps/clkshortcuts/clkinfo.js b/apps/clkshortcuts/clkinfo.js new file mode 100644 index 0000000000..842ae2747c --- /dev/null +++ b/apps/clkshortcuts/clkinfo.js @@ -0,0 +1,45 @@ +(function() { + var storage = require("Storage"); + var storedApps = storage.readJSON("clkshortcuts.json", 1) || {}; + var items = []; + if (Object.keys(storedApps).length !== 0) { + for (var key in storedApps) { + var source = { + name: storedApps[key].name, + img: storedApps[key].icon, + src: storedApps[key].src, + get : function() { + return { + text : this.name, + img : atob(this.img) + } + }, + run: function() { load(this.src);}, + show : function() {}, + hide : function() {}, + } + items.push(source); + } + } + else { + var source = { + name: "Shortcuts", + img: "GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgYGBgYGBgYGBgYGBn/mBn/mBgYGBgYGBgYGBgYGBgAGBgAGB//+A//8AAAAAAAAAAAAA==", + src: "clkshortcuts.app.js", + get : function() { + return { + text : this.name, + img : atob(this.img) + } + }, + run: function() { load(this.src);}, + show : function() {}, + hide : function() {}, + }; + items = [source]; + } + return { + name: "Shortcuts", + items: items + }; +}) \ No newline at end of file diff --git a/apps/clkshortcuts/example_shortcuts_screenshot.png b/apps/clkshortcuts/example_shortcuts_screenshot.png new file mode 100644 index 0000000000..b60a57f574 Binary files /dev/null and b/apps/clkshortcuts/example_shortcuts_screenshot.png differ diff --git a/apps/clkshortcuts/metadata.json b/apps/clkshortcuts/metadata.json new file mode 100644 index 0000000000..3b58bcb483 --- /dev/null +++ b/apps/clkshortcuts/metadata.json @@ -0,0 +1,17 @@ +{ "id": "clkshortcuts", + "name": "Shortcuts", + "shortName": "Shortcuts", + "version": "0.01", + "description": "Add shortcuts to launch your favourite apps straight from the Clock", + "icon": "app.png", + "screenshots": [{"url":"add_shortcuts_screenshot.png"}, {"url":"example_shortcuts_screenshot.png"}], + "tags": "clkinfo,clockinfo", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + { "name": "clkshortcuts.app.js", "url": "app.js" }, + { "name": "clkshortcuts.img", "url": "app-icon.js", "evaluate": true }, + {"name":"clkshortcuts.clkinfo.js","url":"clkinfo.js"} + ], + "data": [{"name":"clkshortcuts.json"}] +} \ No newline at end of file diff --git a/apps/clock_info/ChangeLog b/apps/clock_info/ChangeLog index 7d20438996..cf7da2fa13 100644 --- a/apps/clock_info/ChangeLog +++ b/apps/clock_info/ChangeLog @@ -12,4 +12,5 @@ 0.11: Prepend swipe listener if possible 0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle 0.13: Cache loaded ClockInfos so if we have clockInfoWidget and a clock, we don't load them twice (saves ~300ms) -0.14: Check for .clkinfocache and use that if exists (from boot 0.64) \ No newline at end of file +0.14: Check for .clkinfocache and use that if exists (from boot 0.64) +0.15: Fix error when displaying a category with only one clockinfo (fix #3728) diff --git a/apps/clock_info/lib.js b/apps/clock_info/lib.js index 0e20ab8555..cb6a19abb3 100644 --- a/apps/clock_info/lib.js +++ b/apps/clock_info/lib.js @@ -283,7 +283,7 @@ exports.addInteractive = function(menu, options) { //in the worst case we come back to 0 } while(menu[options.menuA].items.length==0); // When we change, ensure we don't display the same thing as another clockinfo if we can avoid it - while ((options.menuB < menu[options.menuA].items.length) && + while ((options.menuB < menu[options.menuA].items.length-1) && exports.clockInfos.some(m => (m!=options) && m.menuA==options.menuA && m.menuB==options.menuB)) options.menuB++; } diff --git a/apps/clock_info/metadata.json b/apps/clock_info/metadata.json index 1d9a73ce3a..9e9079c28b 100644 --- a/apps/clock_info/metadata.json +++ b/apps/clock_info/metadata.json @@ -1,7 +1,7 @@ { "id": "clock_info", "name": "Clock Info Module", "shortName": "Clock Info", - "version":"0.14", + "version":"0.15", "description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)", "icon": "app.png", "type": "module", diff --git a/apps/color_catalog/metadata.json b/apps/color_catalog/metadata.json index 4d49308ef9..e445e31756 100644 --- a/apps/color_catalog/metadata.json +++ b/apps/color_catalog/metadata.json @@ -5,7 +5,7 @@ "version": "0.03", "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", "icon": "app.png", - "tags": "Color,input,buttons,touch,UI", + "tags": "color,input,buttons,touch,ui", "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ diff --git a/apps/confthyttan/ChangeLog b/apps/confthyttan/ChangeLog new file mode 100644 index 0000000000..77d527619d --- /dev/null +++ b/apps/confthyttan/ChangeLog @@ -0,0 +1,6 @@ +0.01: New App! +0.02: update to my current preferences. +0.03: update app list +0.04: change app name "mysetup" -> "anotherconf" +0.05: remove apps that are not "core" to the experience. +0.06: change name "anotherconf" -> "confthyttan" diff --git a/apps/confthyttan/README.md b/apps/confthyttan/README.md new file mode 100644 index 0000000000..e9b952c822 --- /dev/null +++ b/apps/confthyttan/README.md @@ -0,0 +1,31 @@ +# Thyttan's Default Config + +A different default set of apps and configurations. Brings many quality of life improvements. Opinionated based on the creators taste. Read more below before installing. + +## Usage + +Before installing do this: + +1. Backup your current setup (via the "More..." tab of the App Loader) so you can restore it later if you want. +2. Install this app (you'll be prompted about all data being removed from your Bangle) +3. Try it out, switch out apps to your favorites and tweak to your liking! + +## Features + +There will not be a trace of a "Thyttan's Default Config" app on your watch after installation. Only the apps it installed and the configurations. + +On the clock face: +- Swipe right on the screen to open the launcher (Desktop Launcher) - or press the hardware button. +- Swipe left to open a flashlight app. +- Swipe up to open the messages. +- Swipe down for quick access to music and podcast controls. + - (Do a subsequent left or right swipe to enter the listed apps) +- (Check out the "Quick Launch" app readme for more info) + +## Requests + +Add to the espruino/BangleApps issue tracker and mention @thyttan for bug reports and suggestions. + +## Creator + +thyttan diff --git a/apps/confthyttan/app.png b/apps/confthyttan/app.png new file mode 100644 index 0000000000..24ff01b8a4 Binary files /dev/null and b/apps/confthyttan/app.png differ diff --git a/apps/confthyttan/autoreset.json b/apps/confthyttan/autoreset.json new file mode 100644 index 0000000000..18e94a2829 --- /dev/null +++ b/apps/confthyttan/autoreset.json @@ -0,0 +1 @@ +{"mode":0,"apps":[{"name":"Run+","src":"runplus.app.js","files":"runplus.info,runplus.app.js,runplus.img,runplus.settings.js,runplus_karvonen"}],"timeout":10} \ No newline at end of file diff --git a/apps/confthyttan/backswipe.json b/apps/confthyttan/backswipe.json new file mode 100644 index 0000000000..aa66bd5344 --- /dev/null +++ b/apps/confthyttan/backswipe.json @@ -0,0 +1 @@ +{"mode":0,"apps":[{"name":"Calculator","src":"calculator.app.js"},{"name":"SleepLog","src":"sleeplog.app.js"},{"name":"Messages","sortorder":-9,"src":"messagegui.app.js"},{"name":"Messages","sortorder":-9,"src":"messagegui.app.js","files":"messagegui.info,messagegui,messagegui.app.js,messagegui.new.js,messagegui.boot.js,messagegui.img"}],"standardNumSwipeHandlers":5,"standardNumDragHandlers":1} diff --git a/apps/confthyttan/dtlaunch.json b/apps/confthyttan/dtlaunch.json new file mode 100644 index 0000000000..5417cca3b9 --- /dev/null +++ b/apps/confthyttan/dtlaunch.json @@ -0,0 +1 @@ +{"showClocks":true,"showLaunchers":true,"direct":false,"swipeExit":false,"timeOut":"15s"} \ No newline at end of file diff --git a/apps/confthyttan/edgeclk.settings.json b/apps/confthyttan/edgeclk.settings.json new file mode 100644 index 0000000000..bdf875621c --- /dev/null +++ b/apps/confthyttan/edgeclk.settings.json @@ -0,0 +1 @@ +{"buzzOnCharge":true,"monthFirst":true,"twentyFourH":true,"showAmPm":false,"showSeconds":true,"showWeather":false,"stepGoal":10000,"stepBar":true,"weekBar":true,"mondayFirst":true,"dayBar":true,"redrawOnStep":false} diff --git a/apps/confthyttan/fastload.json b/apps/confthyttan/fastload.json new file mode 100644 index 0000000000..beef5d0cc5 --- /dev/null +++ b/apps/confthyttan/fastload.json @@ -0,0 +1 @@ +{useAppHistory:true,disregardQuicklaunch:true,hideLoading:true} \ No newline at end of file diff --git a/apps/confthyttan/lightswitch.json b/apps/confthyttan/lightswitch.json new file mode 100644 index 0000000000..818aa6b07a --- /dev/null +++ b/apps/confthyttan/lightswitch.json @@ -0,0 +1 @@ +{"colors":"011","image":"heart","touchOn":"always","oversize":7,"dragDelay":500,"minValue":0.01,"tapToLock":false,"unlockSide":"","tapSide":"","tapOn":"always","tOut":2000,"minFlash":0.2,"isOn":true,"value":0.9109} \ No newline at end of file diff --git a/apps/confthyttan/messages.settings.json b/apps/confthyttan/messages.settings.json new file mode 100644 index 0000000000..88efa60827 --- /dev/null +++ b/apps/confthyttan/messages.settings.json @@ -0,0 +1 @@ +{vibrateTimeout:10,vibrate:":",vibrateCalls:":::",flash:false} \ No newline at end of file diff --git a/apps/confthyttan/metadata.json b/apps/confthyttan/metadata.json new file mode 100644 index 0000000000..5fd18c7e90 --- /dev/null +++ b/apps/confthyttan/metadata.json @@ -0,0 +1,68 @@ +{ "id": "confthyttan", + "name": "Thyttan's Default Config", + "version":"0.06", + "description": "A different default set of apps and configurations. Brings many quality of life improvements. Opinionated based on the creators taste. Read more below before installing.", + "icon": "app.png", + "type": "defaultconfig", + "tags": "system,configuration,config,anotherconfig,thyttan", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "dependencies" : { + "sched":"app", + "kbmulti":"app", + "messageicons":"app", + "widmsggrid":"app", + "msgwakefup":"app", + "delaylock":"app", + "notify":"app", + "health":"app", + "widminbate":"app", + "podadrem":"app", + "spotrem":"app", + "android":"app", + "widanclk":"app", + "backswipe":"app", + "torch":"app", + "calculator":"app", + "widbt_notify":"app", + "smpltmr":"app", + "clkinfostopw":"app", + "runplus":"app", + "dtlaunch":"app", + "quicklaunch":"app", + "kineticscroll":"app", + "alarm":"app", + "recorder":"app", + "agenda":"app", + "edgeclk":"app", + "autoreset":"app", + "chargent":"app", + "setting":"app", + "fastload":"app", + "boot":"app", + "ateatimer":"app", + "drained":"app" + }, + "storage": [ + {"name":"backswipe.json", + "url":"backswipe.json"}, + {"name":"autoreset.json", + "url":"autoreset.json"}, + {"name":"dtlaunch.json", + "url":"dtlaunch.json"}, + {"name":"fastload.json", + "url":"fastload.json"}, + {"name":"quicklaunch.json", + "url":"quicklaunch.json"}, + {"name":"messages.settings.json", + "url":"messages.settings.json"}, + {"name":"widbt_notify.json", + "url":"widbt_notify.json"}, + {"name":"recorder.json", + "url":"recorder.json"}, + {"name":"edgeclk.settings.json", + "url":"edgeclk.settings.json"}, + {"name":"setting.json", + "url":"setting.json"} + ] +} diff --git a/apps/confthyttan/quicklaunch.json b/apps/confthyttan/quicklaunch.json new file mode 100644 index 0000000000..4edbb083bf --- /dev/null +++ b/apps/confthyttan/quicklaunch.json @@ -0,0 +1 @@ +{lapp:{name:"Show Launcher",sortorder:-12,src:"no source"},rapp:{name:"torch",type:"app",sortorder:-11,src:"torch.app.js"},uapp:{name:"Messages",sortorder:-9,src:"messagegui.app.js"},dapp:{name:"Extension",type:"app",sortorder:-11,src:"quicklaunch.app.js"},tapp:{name:""},dlapp:{name:"PA Remote",src:"podadrem.app.js"},drapp:{name:"Remote for Spotify",src:"spotrem.app.js"},duapp:{name:""},ddapp:{name:"Extension",type:"app",sortorder:-11,src:"quicklaunch.app.js"},dtapp:{name:""},ddlapp:{name:"Alarms",src:"alarm.app.js"},ddrapp:{name:"Run+",src:"runplus.app.js"},dduapp:{name:""},dddapp:{name:"Agenda",src:"agenda.app.js"},ddtapp:{name:""},rlapp:{name:""},rrapp:{name:""},ruapp:{name:""},rdapp:{name:""},rtapp:{name:""},trace:"dr"} diff --git a/apps/confthyttan/recorder.json b/apps/confthyttan/recorder.json new file mode 100644 index 0000000000..f598b41d25 --- /dev/null +++ b/apps/confthyttan/recorder.json @@ -0,0 +1 @@ +{recording:false,period:10,record:["gps","hrm","steps"],file:"recorder.log0.csv"} \ No newline at end of file diff --git a/apps/confthyttan/setting.json b/apps/confthyttan/setting.json new file mode 100644 index 0000000000..ece0c87edf --- /dev/null +++ b/apps/confthyttan/setting.json @@ -0,0 +1 @@ +{ble:true,blerepl:true,log:false,quiet:0,timeout:10,vibrate:true,beep:true,timezone:2,HID:false,clock:"edgeclk.app.js","12hour":false,firstDayOfWeek:1,brightness:0.5,options:{wakeOnBTN1:true,wakeOnBTN2:true,wakeOnBTN3:true,wakeOnFaceUp:false,wakeOnTouch:false,wakeOnTwist:false,twistThreshold:819.2,twistMaxY:-800,twistTimeout:1000,btnLoadTimeout:700},theme:{fg:65535,bg:0,fg2:65535,bg2:8,fgH:65535,bgH:31,dark:true},clockHasWidgets:true,launcher:"dtlaunch.app.js",touch:{x1:6,y1:14,x2:197,y2:178}} diff --git a/apps/confthyttan/widbt_notify.json b/apps/confthyttan/widbt_notify.json new file mode 100644 index 0000000000..fbcd97f76e --- /dev/null +++ b/apps/confthyttan/widbt_notify.json @@ -0,0 +1 @@ +{showWidget:true,buzzOnConnect:false,buzzOnLoss:false,hideConnected:false,showMessage:false,nextBuzz:30000} \ No newline at end of file diff --git a/apps/counter2/ChangeLog b/apps/counter2/ChangeLog index 58eacf6132..d952c505b1 100644 --- a/apps/counter2/ChangeLog +++ b/apps/counter2/ChangeLog @@ -2,3 +2,4 @@ 0.02: Added Settings & readme 0.03: Fix lint warnings 0.04: Fix lint warnings +0.05: Fix on not reading counter defaults in Settings diff --git a/apps/counter2/metadata.json b/apps/counter2/metadata.json index 400abf2679..04b00487f1 100644 --- a/apps/counter2/metadata.json +++ b/apps/counter2/metadata.json @@ -1,7 +1,7 @@ { "id": "counter2", "name": "Counter2", - "version": "0.04", + "version": "0.05", "description": "Dual Counter", "readme":"README.md", "icon": "counter2-icon.png", diff --git a/apps/counter2/settings.js b/apps/counter2/settings.js index f97d49ad32..d74d9269fc 100644 --- a/apps/counter2/settings.js +++ b/apps/counter2/settings.js @@ -18,7 +18,7 @@ "": { "title": "Counter2" }, "< Back": () => back(), 'Default C1': { - value: settings[0], + value: settings.max0, min: -99, max: 99, onchange: v => { settings.max0 = v; @@ -26,7 +26,7 @@ } }, 'Default C2': { - value: settings[2], + value: settings.max1, min: -99, max: 99, onchange: v => { settings.max1 = v; diff --git a/apps/daymoon/ChangeLog b/apps/daymoon/ChangeLog new file mode 100644 index 0000000000..a00fe79829 --- /dev/null +++ b/apps/daymoon/ChangeLog @@ -0,0 +1,5 @@ +0.01: First functional release +0.02: move moon down, rotate sunrise/sunset, shift Hours/minutes to corners +0.03: Change day and night to have different dots +0.04: Update ChangeLog, coerce dark theme +0.05: Add more screenshots, fix bug in dot colors diff --git a/apps/daymoon/README.md b/apps/daymoon/README.md new file mode 100644 index 0000000000..4b43b75aa3 --- /dev/null +++ b/apps/daymoon/README.md @@ -0,0 +1,20 @@ +# DayMoon Circadian +This started out with a goal to recreate the Pebble [Fair Circadian watchface](https://setpebble.com/app/fair-circadian) by Matthew Clark for the Bangle.js2. +It ended up with me making a mostly new watchface that has the moon phase more prominent, but keeps the single dial 24 hour clock with daylight and sunset highlighted. + +This uses the myLocation app to get your latitude and longitude for proper daylight calculations. If your location isn't set in that app, it will default to Nashua, NH. If your sunrise/sunset times aren't making sense, check that first! + +## Future Development +Feature roadmap: + - [x] 0.01 Fix blocking widgets + - [x] 0.03 Day and Night different color markers + - [x] 0.04 Add to App Loader + - [x] 0.05 Add more screenshots with different moon phases + - [ ] 0.06 add Day of week and month display + - [ ] 0.07 Seconds display + - [ ] 0.08 Color Themes (and settings/options) + - [ ] 0.09 Moon display angle represents how it looks in the sky + - [ ] 0.10 custom/bigger/fitted time digits + - [ ] 0.20 clockinfo support? + - [ ] 0.30 Tap/swipe actions? + \ No newline at end of file diff --git a/apps/daymoon/app-icon.js b/apps/daymoon/app-icon.js new file mode 100644 index 0000000000..a87dd3ee2b --- /dev/null +++ b/apps/daymoon/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4kA///hADBg/g/9/zuFA4Ocj+Nj+lgP4vP/o8AokAg8Ag0Ao//Mf4AptF2s+ICqNGulmkVkxF0wwsPo+EuUik13s14DBsIu1Iw4XBlGIs41BsAWKgl4vFnu1okVns+GsgyBtOZC5FkOQOHw1ItGGvGEAQWWhIuIxF2w92vAJDxGIs2JxFpC494wUos9HBQtHo2Sk+XRhFyk65ChWqqvAAoN5wUpuyMHxFos6FBCwOq5vM5sAzF3ygMCAAl3vDQBgEPCwOqr285nggFJw9IGA1os2IEIPz/QXB5m84vMGALbBNYsIBANnFwMz1Uzn/MqtbrhiBTgN2JAkHpCzBgGjmfzC4POGAmIo1nuAXEFwIHB+evmYXBrm89vM9nAhFnogvFZgNghU//8/mf69ns8vM93Ag1ovAvEAwOGUgIsB/55B9lc9ld5yRBtF2swXDw92sgXDR4P65h4B93M7gQBoyQEu0iRwKMBXwX/5p1B9nMr0Ao8ivAXDsUilAXC/4XBnQVBrncrfOgFCkUmC4eykUrC4R1B0f/O4PM53O3kAqUikIXDjci7YXB+Z3B1+qCwPsqoDBgG8kXlC4kV7x3B1//C4XMrovCO4O72NVC4fhiI6BhWqIoIXBIYPN5lc4EArtdjYXDhdd8PAC4M/18/F4PM4vF53Agsd7sQC4nB7jLBUgKOB1XFLgIAB4EBMgIzBAAUBiPBWYK+CAANc5wXCgHF8sdE4IvD5mx8JgCAAXO9neFwMAjobBI4kA2vMqOwGAnO9yoBgEF3fBroWEgHsjflWAIwD4tc6ouB5nc8sRC4sO9cd9wPBDAXlFwUO8ve8PQC4sAiPCldbAoIYB5hvC50SlfOCw0AivSkNb2IKF2Mc2Uu9YXHh0V9nrjbjEjsc3sV2qEBAAsigEe53hj3R3vbiMeitbfgK0BCAIADgQCBhtc2O7qOx8si7ns4PNqvgCAQXEDwUO93uj1c2Uilvh8vsaYIQDF4kgAgO+qsRjdSkUrjZKBCAxfEAAXt4PR5kijle5gQIABFdrxeBCBgA/ADg=")) \ No newline at end of file diff --git a/apps/daymoon/app.js b/apps/daymoon/app.js new file mode 100644 index 0000000000..ce403fdfeb --- /dev/null +++ b/apps/daymoon/app.js @@ -0,0 +1,260 @@ +const LOCATION_FILE = "mylocation.json"; +let location; + +var Utils = require("graphics_utils"); +var SunCalc = require("suncalc"); +var RADII = { + moon: 40, + arcMin: 48, + arcMax: 63, + dots: 55, + needle: 54, +}; +var COL = { + moon: 65535, // + txture: 33792, // 0.5 ,0.5,0 + shadow: 8196, // .125,0, .125 + day: 40159, //0.6, 0.6,1 + night: 6, // 0, 0, 0.2 + ndots: 2047, // 0, 1, 1 cyan + ddots: 0, + needle: 63488, // 1, 0, 0 red + stime: 2047 +}; +const TAU = 2.0 * Math.PI; +const MX = g.getWidth() / 2, + MY = 24 + 3 + RADII.arcMax; +const DAY_MILLIS = 86400000; +const M_POS = { + x: MX, + y: MY, + r: RADII.moon +}; +// images +const moon_texture = { + width: 80, + height: 80, + bpp: 1, + transparent: 0, + buffer: require("heatshrink").decompress(atob("ABsRqAJHkEiBA0N0uq1AIEgNVqtRqoJEgUiAAQJEioTBAAIzEl2q12oxATECQdVioJD/eqne60UCHQoADoAJBgf+xWrFIOACYUFCYo8Cj/73f70er0ROHAANUBIM//3///q1WIFAV1qtXCggJB//7CYO6keikBOHKAUDCIInClSgCgonBu4TK1W73ShBMQxkCh5OC//uFIInBi91q5PFCYISC3er//iOwXVE41UCYf+9//9AnCJopVBqEv/+/3//E4P6kUgJw4nDKAP+14TB1Xoq4hBEwYFBqgnB3Wr3e737KB/QnIqp3B32OKAYTBE4Z4BAoYnBEoRSC0fyE5ITBJ4WuCYP4J4J3CeQQFClbvBJgOqn5kBnRPKTwJMB1B4B92qEgQACJ4JTBkYnBYwOilYsBO5NUhYmB9+qxGC9TxBEYTvFqki3Y8B1Uikei3+oionIgGrO4OqwGC9H/xATK1/7E4UAnU7kATIqEAl/uE4WA12u0ATJgSgB/+ikUgnW70EFCY9AgGDE4PowEAlWowEBCZJ4BneggUgkRSBCZEAgEKJoIEBgEIAYQOCKYcVBIMqJgIEBgQSCgAQBqiJDRQIOBEwYAEMgNRiITBqKKBCYJJBE4xQGMQIABlBPHHgInDHQQjEAQJTCHgbFEABg8EBg5SDCgxNDABI=")) +}; +const needle = { + width: 23, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob("//+B///D///AAAHgAADgAAHAAA9///j//+H//gA=") +}; + +/* + now use SunCalc.getMoonIllumination() + previously used these: + https://github.com/espruino/BangleApps/blob/master/apps/widmp/widget.js + https://github.com/deirdreobyrne/LunarPhase + modified to be based on millisec instead of sec, and to use tau = 2*pi +*/ + +// requires the myLocation app +function loadLocation() { + location = require("Storage").readJSON(LOCATION_FILE, 1) || { + "lat": 45, + "lon": -71.3, + "location": "Nashua" + }; //{"lat":51.5072,"lon":0.1276,"location":"London"}; +} + +function drawMoon(shadowShape) { + g.setColor(0, 0, 0).fillCircle(MX, MY, RADII.arcMax + 3); + g.setColor(COL.moon).fillCircle(MX, MY, RADII.moon - 1); + g.setColor(COL.txture).drawImage(moon_texture, MX, MY, { + rotate: 0 + }); + // TODO: can set the rotation here to the parallacticAngle from getMoonPosition + g.setColor(COL.shadow).fillPoly(shadowShape); + // TODO: set rotation of the fillPoly? parallactic-mp.angle I think. + // Use g.transformVertices to do the rotation +} + +function drawDayRing(times) { + let r_ = RADII.arcMin; + let rm = RADII.arcMax; + let rd = RADII.dots; + let radT = [tToRad(times[0]), tToRad(times[1])]; + let hhmm = [require("locale").time(times[0], 1), require("locale").time(times[1], 1)]; + g.setColor(COL.day); + Utils.fillArc(g, MX, MY, r_, rm, radT[0], radT[1]); + g.setColor(COL.night); + Utils.fillArc(g, MX, MY, r_, rm, radT[1] - TAU, radT[0]); + // write sunrise/sunset times + g.setFont('6x8').setColor(COL.stime); + g.setFontAlign(0, 1, 3).drawString(hhmm[0], MX - rm - 2, MY); + g.setFontAlign(0, 1, 1).drawString(hhmm[1], MX + rm + 2, MY); + // draw dots + let edges = []; + let isDay = false; + let flag = false; + if (radT[1] > TAU) { + edges = [radT[1] - TAU, radT[0]]; + g.setColor(COL.ddots); + isDay = true; + } else { + edges = [radT[0], radT[1]]; + g.setColor(COL.ndots); + isDay = false; + } + for (var i = 0; i < 24; i++) { + let a = i * TAU / 24; + if (!flag && a > edges[0] && a < edges[1]) { + //first cross + if (isDay) { + g.setColor(COL.ndots); + } else { + g.setColor(COL.ddots); + } + flag = true; + } else if (flag && a > edges[1]) { + //second cross + if (isDay) { + g.setColor(COL.ddots); + } else { + g.setColor(COL.ndots); + } + flag = false; + } + let dotSize = (i % 3 == 0) ? 2 : 1; + let pX = MX + Math.cos(a) * rd; + let pY = MY + Math.sin(a) * rd; + g.fillCircle(pX, pY, dotSize); + } + let labels = ['6P', '12A', '6A', '12P']; + let qX = [rd - 9, 2, 11 - rd, 2]; + let qY = [1, rd - 10, 1, 12 - rd]; + g.setFont('4x6').setFontAlign(0, 0, 0).setColor(COL.ndots); + for (var j = 0; j < 4; j++) { + g.drawString(labels[j], MX + qX[j], MY + qY[j]); + } +} + + +function drawHHMM(d) { + var HM = require("locale").time(d, 1 /*omit seconds*/ ).split(":"); + // write digital time + g.setBgColor(0, 0, 0).setColor(1, 1, 1).setFontVector(45); + g.setFontAlign(1, 1, 0).drawString(" " + HM[0], MX - 20, g.getHeight() + 3); + g.setFontAlign(-1, 1, 0).drawString(HM[1] + " ", MX + 30, g.getHeight() + 3); + // TODO: use the meridian text AM/PM or blank for 24 hr. + // var meridian = require("locale").meridian(d); +} + +function moonShade(pos, mp) { + pos = pos !== undefined ? pos : M_POS; + mp = mp !== undefined ? mp : SunCalc.getMoonIllumination(new Date()); + // pos has x,y, r for the drawing, mp is from SunCalc Moon Illumination + let k = mp.fraction; + // k is the percent along the equator of the terminator + const pts = Math.min(pos.r >> 1, 32); + // this gives r/2 pts on the way down and up, capped at 64 total for polyfill + let a = [], + b = [], + s1 = 1, + s2 = 0; + // scale s1 is 1 or -1 for fixed edge of the shadow; defined via case switches below + // scale s2 factor for the moving edge of the shadow + // need to do some computation to simplify for new/full moon if k 'close enough' to 0 or 1/-1 + // + let isWaxing = (mp.phase < 0.5); + s1 = isWaxing ? -1 : 1; + s2 = isWaxing ? 1 - 2 * k : 2 * k - 1; + let tr = (pos.r + 1); + for (var i = 0; i < pts; i++) { + // down stroke on the outer shadow + var t = i * Math.PI / (pts + 1); //pts+1 so we leave the last point for the starting of going up + let cirX = Math.sin(t) * tr; + let cirY = Math.cos(t) * tr; + a.push(pos.x + s1 * cirX); //x + a.push(pos.y + cirY); //y + b.push(pos.x + s2 * cirX); //x for shadow edge + b.push(pos.y - cirY); //y going up for shadow edge + } + return a.concat(b); +} + +function tToRad(date) { + date = (date !== undefined) ? new Date(date.getTime()) : new Date(); + let milli = date - new Date(date.setHours(0, 0, 0, 0)); + return (milli / DAY_MILLIS + 0.25) * TAU; +} + +function draw(date) { + var d = date !== undefined ? date : new Date(); + var a = tToRad(d), + shape = moonShade(M_POS, SunCalc.getMoonIllumination(d)), + sTimes = SunCalc.getTimes(d, location.lat, location.lon), + daylight = [sTimes.sunrise, sTimes.sunset]; + //clear time area + g.clearRect(Bangle.appRect); //g.setColor(0).fillRect(0, 176 - 45, 176, 176); + drawMoon(shape); + drawDayRing(daylight); + drawHHMM(d); + // draw pointer + // TODO: Maybe later make this an overlay that can be removed?? -avoid drawing so much every minute/second + g.setColor(COL.needle).drawImage(needle, MX + RADII.needle * Math.cos(a), MY + RADII.needle * Math.sin(a), { + rotate: a + }); + +} +/* +const shotTimes = [1720626960000, 1729184400000, 1738298880000, 1717575420000]; +let desc =`first quarter -2 days moon at 10:20 in the summer + jun 10 2024 10:56 +full moon at 12 noon near fall equinox + Sep 17 2024 12:00 +new moon at 11pm in winter + dec 30 2024 23:48 +3rd quarter moon at 03:17 am + May 5 2024 03:17` + +function screenshots(times) { + let d = new Date(); + for (let t of times) { + d.setTime(t); + draw(d); + g.dump(); + } +} +*/ +// Clear the screen once, at startup +g.reset(); +// requires the myLocation app +loadLocation(); +g.setBgColor(0, 0, 0).clear(); +// draw immediately at first +draw(); +// now draw every second +// eventually maybe update the moon just every hour?? +var secondInterval = setInterval(draw, 10000); //was 1000 +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', on => { + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 10000); //was 1000 + draw(); // draw immediately + } +}); +/* Show launcher when middle button pressed +This should be done *before* Bangle.loadWidgets so that +widgets know if they're being loaded into a clock app or not */ +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +g.setTheme({ + fg: "#fff", + bg: "#000", + fg2: "#fff", + bg2: "#004", + fgH: "#fff", + bgH: "#00f", + dark: true +}); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/daymoon/daymoon.png b/apps/daymoon/daymoon.png new file mode 100644 index 0000000000..f9cd841782 Binary files /dev/null and b/apps/daymoon/daymoon.png differ diff --git a/apps/daymoon/metadata.json b/apps/daymoon/metadata.json new file mode 100644 index 0000000000..f06e216bc7 --- /dev/null +++ b/apps/daymoon/metadata.json @@ -0,0 +1,17 @@ +{ "id": "daymoon", + "name": "DayMoon Circadian Clock", + "version": "0.05", + "dependencies": {"mylocation":"app"}, + "description": "A 24 hour clockface showing the Moon Phase and portion of the day that the Sun is up inspired by Matthew Clark's *Fair Circadian* Pebble watchface", + "icon": "daymoon.png", + "screenshots": [{"url":"s1.png"},{"url":"s2.png"},{"url":"s3.png"},{"url":"s4.png"}], + "type": "clock", + "tags": "clock,moon,lunar", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme":"README.md", + "storage": [ + {"name":"daymoon.app.js","url":"app.js"}, + {"name":"daymoon.img","url":"app-icon.js","evaluate":true} + ] +} \ No newline at end of file diff --git a/apps/daymoon/s1.png b/apps/daymoon/s1.png new file mode 100644 index 0000000000..cb64c2f012 Binary files /dev/null and b/apps/daymoon/s1.png differ diff --git a/apps/daymoon/s2.png b/apps/daymoon/s2.png new file mode 100644 index 0000000000..22eddc14bf Binary files /dev/null and b/apps/daymoon/s2.png differ diff --git a/apps/daymoon/s3.png b/apps/daymoon/s3.png new file mode 100644 index 0000000000..463be5d216 Binary files /dev/null and b/apps/daymoon/s3.png differ diff --git a/apps/daymoon/s4.png b/apps/daymoon/s4.png new file mode 100644 index 0000000000..6b8ce5021d Binary files /dev/null and b/apps/daymoon/s4.png differ diff --git a/apps/delaylock/metadata.json b/apps/delaylock/metadata.json index dff4d92198..7441d822be 100644 --- a/apps/delaylock/metadata.json +++ b/apps/delaylock/metadata.json @@ -3,7 +3,7 @@ "version":"0.01", "description": "Delay the locking of the screen to 5 seconds after the backlight turns off.", "icon": "app.png", - "tags": "settings, configuration, backlight, touchscreen, screen", + "tags": "settings,configuration,backlight,touchscreen,screen", "type": "bootloader", "supports" : ["BANGLEJS2"], "readme": "README.md", diff --git a/apps/denseclock/ChangeLog b/apps/denseclock/ChangeLog new file mode 100644 index 0000000000..7ae520fa5f --- /dev/null +++ b/apps/denseclock/ChangeLog @@ -0,0 +1,3 @@ +0.01: Begin rewrite from old code. +0.02: Changed visuals: uA > mA, info order, battery state indication +0.03: Update app icon diff --git a/apps/denseclock/app-icon.js b/apps/denseclock/app-icon.js new file mode 100644 index 0000000000..b8a0761f89 --- /dev/null +++ b/apps/denseclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcBkmSpIC/AS0nwEHCh5yBggROgP4jgROh1ICKWT4Hkz0AC4NPgFypPgAQIRDyBLBCIMAiVAgECpAGBCInwn4RBg4dBoH/yV4j+ACI0Dz05kARB8gRJgARFgYRBgEB5IRKBwICCI4/8CIdJ/kCGoP+CIMPUIIRBkgRIYop9DgJHCPowDBTwUAyAREiSkBCITCOAX4CuwE5kmT4D1BBYSMByShBhwRCgEkWYQRDUIIRCUIWfEALXBCIsDCIMf+QICvEECIILBBAV5CIUcBAYRFpEEBYIRKnARIgFyHwfk+PAGogREgQIBPQMk+EACI9J/hrCyUHCIMHCJEnwAIByEBCIJrFCI/wWYIROwP5CIShECI7FBgjFDPoTFBgTXGBYICCCI6PDAX4C/ARoA=")) diff --git a/apps/denseclock/app.js b/apps/denseclock/app.js new file mode 100644 index 0000000000..62a40154e6 --- /dev/null +++ b/apps/denseclock/app.js @@ -0,0 +1,157 @@ +// FONTS + +/* + Share Tech Mono: https://fonts.google.com/specimen/Share+Tech+Mono + Converted with: https://www.espruino.com/Font+Converter +*/ + +Graphics.prototype.setFontShareTechMonoBig = function(scale) { + // Actual height 56 (55 - 0) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAAAB+AAAAAAAAB+AAAAAAAAB+AAAAAAAAB+AAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAHwAAAAAAAA/wAAAAAAAD/wAAAAAAAf/wAAAAAAB//gAAAAAAP/8AAAAAAA//wAAAAAAH/+AAAAAAA//4AAAAAAD//AAAAAAAf/8AAAAAAB//gAAAAAAP/8AAAAAAB//wAAAAAAH/+AAAAAAA//4AAAAAAD//AAAAAAAf/4AAAAAAB//gAAAAAAP/8AAAAAAA//wAAAAAAA/+AAAAAAAA/4AAAAAAAA/AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///4AAAAD/////AAAAP/////wAAAf/////4AAA//////8AAA//////8AAB/AAB/j+AAB+AAH/B+AAB8AAP8A+AAB8AA/4A+AAB8AB/gA+AAB8AH/AA+AAB8AP8AA+AAB8A/4AA+AAB8B/gAA+AAB+D/AAB+AAA//+AAP8AAA//////8AAAf/////4AAAP/////wAAAH/////gAAAB////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAeAAAfgAAAA+AAAfAAAAA+AAA/AAAAA+AAA+AAAAA+AAA+AAAAA+AAB+AAAAA+AAB8AAAAA+AAB//////+AAB//////+AAB//////+AAB//////+AAB//////+AAB//////+AAAAAAAAA+AAAAAAAAA+AAAAAAAAA+AAAAAAAAA+AAAAAAAAA+AAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAB8AAAAD+AAB8AAAAP+AAB8AAAAf+AAB8AAAB/+AAB8AAAD/+AAB8AAAH/+AAB8AAAf8+AAB8AAA/4+AAB8AAD/w+AAB8AAH/A+AAB+AAf+A+AAB+AA/4A+AAA////wA+AAA////gA+AAAf//+AA+AAAP//8AA+AAAH//wAA+AAAB/+AAA+AAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAA+AAB8AAAAA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AD8AA+AAB+AH+AB+AAA////gD+AAA//////8AAAf/////8AAAP//P//4AAAH/+H//wAAAA/4D//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAP/wAAAAAAD//wAAAAAB///wAAAAAf///wAAAAH////wAAAB///+HwAAAB///gHwAAAB//wAHwAAAB/4AAHwAAAB+AAAHwAAABAAAAHwAAAAAAP///+AAAAAf///+AAAAAf///+AAAAAf///+AAAAAf///+AAAAAf///+AAAAAAAHwAAAAAAAAHwAAAAAAAAHwAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///wAA+AAB///wAA+AAB///wAA+AAB///wAA+AAB///wAA+AAB///wAA+AAB8AHwAA+AAB8AHwAA+AAB8AHwAA+AAB8AD4AA+AAB8AD4AA+AAB8AD4AB+AAB8AD+AD+AAB8AD///8AAB8AB///8AAB8AA///4AAB8AAf//wAAB4AAP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AAAAP/////gAAAf/////4AAAf/////4AAA//////8AAA/APgAD+AAB+APgAB+AAB8APgAA+AAB8APgAA+AAB8APgAA+AAB8APgAA+AAB8APgAA+AAB8APgAA+AAB8AHwAA+AAB8AHwAB+AAB8AH+AP8AAB8AH///8AAB8AD///4AAAAAB///wAAAAAA///gAAAAAAP/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAB8AAAAAAAAB8AAAAAAAAB8AAAAAAAAB8AAAAAOAAB8AAAAD+AAB8AAAAf+AAB8AAAD/+AAB8AAA//+AAB8AAH//+AAB8AB///gAAB8AP//8AAAB8D///AAAAB8f//4AAAAB////AAAAAB///wAAAAAB//+AAAAAAB//gAAAAAAB/8AAAAAAAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAD/8H//wAAAP//P//4AAAf/////8AAA//////8AAA//////+AAB/AP+AB+AAB8AD8AA+AAB8AD8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AB8AA+AAB8AD8AA+AAB+AH+AA+AAA////AD+AAA//////8AAAf/////8AAAP//P//4AAAH/+H//wAAAA/4D//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAAAP//+AAAAAAf///AAAAAAf///AA+AAA////gA+AAB/AAfgA+AAB+AAPgA+AAB8AAPwA+AAB8AAHwA+AAB8AAHwA+AAB8AAHwA+AAB8AAHwA+AAB8AAHwA+AAB8AAHwB+AAB+AAHwB+AAA//////8AAA//////8AAAf/////4AAAP/////wAAAH/////gAAAB////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAB+AAAAB+AAB+AAAAB+AAB+AAAAB+AAB+AAAAB+AAB+AAAAB+AAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), + 46, + 32, + 60+(scale<<8)+(1<<16) + ); + return this; +}; + +Graphics.prototype.setFontShareTechMono = function(scale) { + // Actual height 38 (37 - 0) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAAAeAAAAAB4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAfAAAAAP8AAAAD/gAAAB/4AAAAf+AAAAP/AAAAH/gAAAB/4AAAA/8AAAAP/AAAAH/gAAAD/wAAAA/8AAAAP+AAAAA/gAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//4AAA///8AAP///8AA////wAHwAfvgAeAH4eABwA/A4AHAHwDgAcB+AOAB4PgB4AHj8AHgAf///+AA////wAB///+AAD///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAOAA8AAA4ADwAADgAeAAAOAB4AAA4AH////gAf///+AB////4AH////gAAAAAOAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AHAAAfgAcAAD+ABwAA/4AHAAH/gAcAA/OABwAP44AHgB+DgAfAfwOAA//8A4AD//gDgAH/4AOAAH+AA4AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAOABwBwA4AHAHADgAcAcAOABwBwA4AHAHADgAeA+AOAB8H4B4AD////gAP///8AAf+f/gAAPgf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAP+AAAAP/4AAAf//gAAf//OAAB//A4AAH+ADgAAcAAOAAAAH//4AAA///gAAD//+AAAP//4AAAAHgAAAAAOAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/+ADgAf/4AOAB//gA4AH/+ADgAcA4AOABwDgA4AHAPADgAcA8AeABwD//4AHAH//AAcAP/4AAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAH///4AA////wAH////gAeB4AeABwHgA4AHAeADgAcB4AOABwHgA4AHAeAHgAcA//+ABwD//wAAAH/+AAAAP/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAABwAAAAAHAAAAgAcAAAeABwAAf4AHAAP/gAcAH/+ABwH/+AAHD//AAAf//AAAB//gAAAH/gAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAP+f/gAD////AAP///+AB//8B4AHgPgDgAcAcAOABwBwA4AHAHADgAcAcAOAB4D4B4AH////gAP///8AAf/f/wAAfw/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAH/+AAAA//8A4AH4HwDgAeAHgOABwAeA4AHAB4DgAcAHgOABwAeB4AHgB4HgAf///+AA////wAB///+AAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AHgAAHgAeAAAeAB4AAB4AHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='), + 46, + 22, + 40+(scale<<8)+(1<<16) + ); + return this; +}; + +Graphics.prototype.setFontShareTechMonoSmall = function(scale) { + // Actual height 23 (22 - 0) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/zgP/zgP/zgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAPwAAPwAAAAAAPwAAPwAAPAAAAAAAAAAAAAAAAAAAAAAAAwYAP//gP//gP//gAwYAP//gP//gP//gAwYAAAAAAAAAAAAAAAAAB4AAD+BgH/Bg+HB8+DB8+DB8GD/gGB/AAAcAAAAAAAAAABAAH7AAP7AAMLAAMbAAP7AAHzfAAG/gAGxgAGxgAG/gAGfAAGAAAAAAAAAADz/AH//gP+DgMOBgMMBgMMBgMP/gAP/gAMAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAPwAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wP//8/AA+4AAGgAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAC4AAG/AB+P//8D//wAAAAAAAAAAAAAAAAAAAAAAAADAAADuAAB+AAP4AAfwAAP8AAB+AADsAADAAAAAAAAAAAAAAAAAAAADgAADgAADgAAf8AAf8AAf8AADgAADgAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAD+AAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAADgAADgAADgAADgAADgAADgAADgAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAADgAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAeAAD8AAfwAB+AAP4AA/AAH8AAfgAA8AAAwAAAAAAAAAAAAAAAD/+AH//APB/gMDxgMHBgMeBgP//gH//AD/+AAAAAAAAAAAAAAAAAGAAgGABgOABgOABgP//gP//gAABgAABgAABgAAAAAAAAAAAAAAAAAABgMAHgMAPgMA9gMB5gOHxgH/BgD+BgAABgAAAAAAAAAAAAAAAAAAAAMCBgMGBgMGBgMHBgP/DgH//gD5/AAAAAAAAAAAAAAAAAAAAAAD4AA/4AP/4APwYAMAYAAP/gAP/gAAcAAAYAAAAAAAAAAAAAAAAAAAAAP+BgP+BgMGBgMGBgMHDgMH/gMD/AAAAAAAAAAAAAAAAAAAAAD/+AH//AP//gMMBgMMBgMOBgMP/gMH/AAD+AAAAAAAAAAAAAAAAAMAAAMAAAMADgMAfgMH/AM/4AP/AAPwAAGAAAAAAAAAAAAAAAAAAAD5/AH//gP/DgMHBgMGBgMHBgP/jgH//gD5/AAAAAAAAAAAAAAAAAD+AAH/AgP/hgMBhgMBhgMBhgP//gH//AD/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcDgAcDgAcDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAcD+AcD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAHgAAPwAAMwAAc4AAY4AA4cAA4cABwMAAAAAAAAAAAAAAAAAAMYAAMYAAMYAAMYAAMYAAMYAAMYAAMYAAMYAAAAAAAAAAAAAAAAABwMAA4cAA4cAAY4AAc4AAMwAAPwAAHgAAHgAAAAAAAAAAAAAAAAAAAAAMAAAMABgMDzgMHzgOeAAH8AAD4AAAAAAAAAAAAAAB/8AH//AP//gMHhgMf5gM/9gMwNgM/5gM/8gMAMAP/8AH/8AA/wAAAAAAADgAA/gAP/gD/4AP8YAPAYAP8YAD/4AAP/gAA/gAADgAAAAAAAAAAAAP//gP//gMGBgMGBgMGBgMGBgP/DgH//gD5/AAAAAAAAAAAAAAAAAB/8AH//AP//gOABgMABgMABgMABgMABgMABgAAAAAAAAAAAAAAAAP//gP//gMABgMABgMABgMABgOADgH//gH//AAAAAAAAAAAAAAAAAP//gP//gP//gMGBgMGBgMGBgMGBgMGBgMABgAAAAAAAAAAAAAAAAP//gP//gP//gMHAAMHAAMHAAMHAAMHAAMAAAAAAAAAAAAAAAAAAAD/+AH//AP//gMABgMDBgMDBgMD/gMD/gAD/gAAAAAAAAAAAAAAAAP//gP//gAHAAAHAAAHAAAHAAAHAAP//gP//gAAAAAAAAAAAAAAAAAAAAMABgMABgP//gP//gP//gMABgMABgAAAAAAAAAAAAAAAAAAAAAAAAAABgMABgMABgMABgMAHgP//AP/+AAAAAAAAAAAAAAAAAAAAAP//gP//gAPAAAfgAB/4ADw+APAfgOAHgIABgAAAAAAAAAAAAAAAAAAAAP//gP//gAABgAABgAABgAABgAABgAAAAAAAAAAAAAAAAP//gP//gPgAAP+AAB/gAAHwAB/gAP8AAPgAAP//gP//gAAAAAAAAAAAAP//gP//gPwAAP/AAA/8AAD/gAAPgP//gP//gAAAAAAAAAAAAA/8AH//AH//gOADgMABgMABgMABgOADgH//gH//AA/4AAAAAAAAAAAAAP//gP//gMDgAMDgAMDgAMDgAP/AAH/AAD+AAAAAAAAAAAAAAA/8AH//AH//gOADgMABgMABgMABgOADgH//gH//wA/4wAAAAAAAAH//gP//gP//gMDAAMDAAMDgAOD8AP//AH+PgB4DgAAAgAAAAAAAAAAAAD4AAH+BgP+BgOGBgMHBgMHBgMH/gMD/AAA+AAAAAAAAAAAAAMAAAMAAAMAAAMAAAP//gP//gP//gMAAAMAAAMAAAMAAAAAAAAAAAAAAAP//AP//gAADgAABgAABgAABgAADgP//gP//AAAAAAAAAAAAAMAAAP4AAP/wAA//AAB/gAAHgAD/gB/+AP/AAPwAAIAAAAAAAAAAAP/gAP//gAP/gAAfgAH+AAHwAAD/gAAfgAf/gP//gP8AAAAAAAAAAAAAgOADgPgPgH4+AB/4AAfwAB/4AH4/APgPgOADgAAAgAAAAAAAAMAAAPAAAPwAAD8AAA//gAP/gAf/gD8AAPwAAPAAAIAAAAAAAAAAAAAAAMADgMAPgMA/gMD5gMPhgM+BgP4BgPgBgOABgAAAAAAAAAAAAAAAAAAAAAAAA///+///+wAAGwAAGwAAGAAAAAAAAAAAAAAAAAAAAwAAA+AAAfgAAH8AAA/AAAP4AAB+AAAPwAAD8AAAeAAAGAAAAAAAAAAAAAAAAAAAAwAAGwAAGwAAG///+///+AAAAAAAAAAAAAAAAAAAAAAAAA4AAD4AAPwAAeAAAYAAAeAAAPwAAD4AAAYAAAAAAAAAAAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAAAAAAAAAAAAAAIAAAMAAAOAAAGAAACAAAAAAAAAAAAAAAAAAAAAAAAAOAAY/gAY/gAZxgAZxgAZxgAZxgAf/gAP/gAABgAAAgAAAAAAAAAAAAP//gP//gAcBgAYBgAYBgAYBgAcDgAf/gAP/AAAAAAAAAAAAAAAAAAAAAAP/AAf/gAYBgAYBgAYBgAYBgAYBgAAAAAAAAAAAAAAAAAAAAAP/AAf/gAcDgAYBgAYBgAYBgAYDgP//gP//gAAAAAAAAAAAAAAAAAP+AAf/gAf/gAYxgAYxgAYxgAfxgAPxgAHwAAAAAAAAAAAAAAAAAAYBgAYBgD//gP//gP//gMYBgMYBgMYBgMQAAAAAAAAAAAAAAAAAAAPwGAf/GAYfGAYfGAYfGAYfGAY7mAf7+Afx8AAAAAAAAAAAAAAAAP//gP//gAcAAAYAAAYAAAYAAAcAAAf/gAP/gAAAAAAAAAAAAAAAAAYAAAYAAAYAAMf/gOf/gMf/gAABgAABgAABgAAAAAAAAAAAAAAAAAAAAAAAGAYAGAYAGAYAGMf/+Of/+Mf/8AAAAAAAAAAAAAAAAAAAAP//gP//gP//gADwAAH8AAefAAcHgAYDgAQAgAAAAAAAAAAAAEAAAMAAAMAAAMAAAP//AP//gAADgAABgAABgAABgAAAgAAAAAAAAAf/gAf/gAYAAAYAAAf/gAf/gAf/gAYAAAYAAAf/gAP/gAAAAAAAAAAAAAf/gAf/gAcAAAYAAAYAAAYAAAcAAAf/gAP/gAAAAAAAAAAAAAAAAAP/AAf/gAcDgAYBgAYBgAYBgAcDgAf/gAP/AAAAAAAAAAAAAAAAAAf/+Af/+AcBgAYBgAYBgAYBgAcDgAf/gAP/AAAAAAAAAAAAAAAAAAP/AAf/gAcDgAYBgAYBgAYBgAYDgAf/+Af/+AAAAAAAAAAAAAAAAAYBgAYBgAf/gAf/gAcBgAYBgAYBgAYAAAYAAAAAAAAAAAAAAAAAAAAAAAPhgAfxgAZxgAYxgAYxgAY/gAYfAAAAAAAAAAAAAAAAAAAAAAYAAAYAAAYAAD//AD//gAYBgAYBgAYBgAYBgAAAAAAAAAAAAAAAAAf+AAf/gAf/gAABgAABgAABgAf/gAf/gAf/gAAAAAAAAAAAAAQAAAeAAAf4AAH/AAAfgAADgAAfgAH/AAf4AAeAAAQAAAAAAAAAAAfAAAf/AAD/gAAPgAD/gAH4AAH/gAAPgAD/gAf+AAfAAAAAAAAAAAAAAAYBgAcHgAfPAAH+AAD4AAH+AAfPgAcDgAQBgAAAAAAAAAAAAAQAAAeAAAfwGAH+GAA/uAAD+AAf8AH/AAf4AAeAAAQAAAAAAAAAAAAAAAABgAYHgAYPgAY/gAZ5gAfxgAfBgAeBgAYAAAAAAAAAAAAAAAAAAAAAAADgAADgAf//8/+/+4AAGwAAGwAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//+///+f//+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAGwAAG4AAG/+/+f//8ADgAADgAAAAAAAAAAAAAAAAAAAAAADgAADAAADAAADAAADAAADgAABgAADgAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), + 32, + atob("DQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0H"), + 24+(scale<<8)+(1<<16) + ); + return this; +}; + +{ + + // VARS + + let FONT_NAME = "ShareTechMono"; + let BIG_FONT_HEIGHT = 60; + //let NORMAL_FONT_HEIGHT = 40; + let SMALL_FONT_HEIGHT = 24; + + let timeDrawTimeout; + let infoDrawTimeout; + let lockState = Bangle.isLocked(); + let pressure; + + + + // LISTENERS + + Bangle.on('lock', function(isLocked) { + lockState = isLocked; + timeDraw(); + infoDraw(); + }); + + + + // DRAW FUNCTIONS + + let timeDraw = function() { + g.reset(); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y + BIG_FONT_HEIGHT); + + var date = new Date(); + var timeArray = [date.getHours().toString().padStart(2, "0"), + date.getMinutes().toString().padStart(2, "0")]; + if (!lockState) timeArray.push(date.getSeconds().toString().padStart(2, "0")); + var timeString = timeArray.join(":"); + g.setFontAlign(0, 0).setColor(g.theme.fg).setFont(FONT_NAME + (lockState ? "Big" : "")); + g.drawString(timeString, Bangle.appRect.x2/2, Bangle.appRect.y + BIG_FONT_HEIGHT/2); + + if (timeDrawTimeout) clearTimeout(timeDrawTimeout); + timeDrawTimeout = setTimeout(function() { + timeDrawTimeout = undefined; + timeDraw(); + }, (lockState ? 10000 - (Date.now() % 10000) : 1000 - (Date.now() % 1000))); // if locked, every clock's 10s, otherwise every 1s + }; + + let infoDraw = function() { + g.reset(); + + var date = new Date(); + var dateString = [date.getFullYear().toString().padStart(4,"0"), + (date.getMonth()+1).toString().padStart(2,"0"), + date.getDate().toString().padStart(2,"0")].join("-"); + + var tzOffset = -(date.getTimezoneOffset())/60; + var tzOffsetString = (tzOffset >= 0 ? "+" + tzOffset : tzOffset); + + var batteryString = (Bangle.isCharging() ? "+" : "") + E.getBattery() + "%"; + + var pressureString = (pressure ? pressure + "hPa" : "(hPa)"); + + var powerString = (E.getPowerUsage().total / 1000) + "mA"; + + var stepsString = Bangle.getHealthStatus("day").steps + "ST"; + + var bluetoothStatus = NRF.getSecurityStatus(); + var bluetoothString = (bluetoothStatus.connected ? bluetoothStatus.connected_addr.split(" ")[0].substr(-5) : "N/C"); + + var infoMatrix = [ + [dateString + tzOffsetString ], + [batteryString, pressureString], + [powerString ], + [stepsString, bluetoothString ] + ]; + + g.clearRect(Bangle.appRect.x, Bangle.appRect.y + BIG_FONT_HEIGHT, Bangle.appRect.x2, Bangle.appRect.y2); + g.setFontAlign(0, -1).setColor(g.theme.fg2).setFont(FONT_NAME+"Small"); + + infoMatrix.forEach((lineArray, lineNumber) => { + g.drawString(lineArray.join(" "), Bangle.appRect.x2/2, Bangle.appRect.y + BIG_FONT_HEIGHT + SMALL_FONT_HEIGHT*lineNumber); + }); + + Bangle.getPressure().then(baroValue => { pressure=Math.round(baroValue.pressure); }); + + if (infoDrawTimeout) clearTimeout(infoDrawTimeout); + infoDrawTimeout = setTimeout(function() { + infoDrawTimeout = undefined; + infoDraw(); + }, (lockState ? 60000 : 10000)); // if locked, a minute from now, otherwise in 10s + }; + + + + // DRAW CALLS + + g.clear(); + + Bangle.setUI({ + mode: "clock", + remove: function() { + if (timeDrawTimeout) clearTimeout(timeDrawTimeout); + timeDrawTimeout = undefined; + if (infoDrawTimeout) clearTimeout(infoDrawTimeout); + infoDrawTimeout = undefined; + + delete Graphics.prototype.setFontShareTechMono; + delete Graphics.prototype.setFontShareTechMonoBig; + delete Graphics.prototype.setFontShareTechMonoSmall; + }}); + + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + timeDraw(); + infoDraw(); +} diff --git a/apps/denseclock/app.png b/apps/denseclock/app.png new file mode 100644 index 0000000000..4f27b473ca Binary files /dev/null and b/apps/denseclock/app.png differ diff --git a/apps/denseclock/metadata.json b/apps/denseclock/metadata.json new file mode 100644 index 0000000000..c361313ffd --- /dev/null +++ b/apps/denseclock/metadata.json @@ -0,0 +1,18 @@ +{ "id": "denseclock", + "name": "Dense Clock", + "shortName":"Dense Clock", + "version":"0.03", + "description": "A clockface dense with text-only information. Switches between showing seconds and minutes when unlocked/locked, in the interest of saving power.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"denseclock.app.js","url":"app.js"}, + {"name":"denseclock.img","url":"app-icon.js","evaluate":true} + ], + "screenshots": [ + {"url":"screenshot_locked.png"}, + {"url":"screenshot_unlocked.png"} + ] +} diff --git a/apps/denseclock/screenshot_locked.png b/apps/denseclock/screenshot_locked.png new file mode 100644 index 0000000000..61a05ca446 Binary files /dev/null and b/apps/denseclock/screenshot_locked.png differ diff --git a/apps/denseclock/screenshot_unlocked.png b/apps/denseclock/screenshot_unlocked.png new file mode 100644 index 0000000000..f1af09cbac Binary files /dev/null and b/apps/denseclock/screenshot_unlocked.png differ diff --git a/apps/dinoClock/metadata.json b/apps/dinoClock/metadata.json index a61ce122b5..1455e84a64 100644 --- a/apps/dinoClock/metadata.json +++ b/apps/dinoClock/metadata.json @@ -6,7 +6,7 @@ "icon": "app.png", "version": "0.01", "type": "clock", - "tags": "clock, weather, dino, trex, chrome", + "tags": "clock,weather,dino,trex,chrome", "supports": ["BANGLEJS2"], "allow_emulator": true, "readme": "README.md", diff --git a/apps/drained/ChangeLog b/apps/drained/ChangeLog index 0667d8ff61..af1ee299b9 100644 --- a/apps/drained/ChangeLog +++ b/apps/drained/ChangeLog @@ -6,3 +6,4 @@ 0.05: Enhance menu: permit toggling bluetooth 0.06: Display clock in green when charging, with "charging" text 0.07: Correctly restore full power when the charged threshold is reached +0.08: Redisplay immediately on changes to charging status diff --git a/apps/drained/app.js b/apps/drained/app.js index cefddbcc7d..9f8f6988f9 100644 --- a/apps/drained/app.js +++ b/apps/drained/app.js @@ -118,6 +118,7 @@ Bangle.on("charging", function (charging) { drainedInterval = clearInterval(drainedInterval); if (charging) drainedInterval = setInterval(checkCharge, interval * 60 * 1000); + draw(); }); if (!keepStartup) { var storage = require("Storage"); diff --git a/apps/drained/app.ts b/apps/drained/app.ts index bd79ebcab9..57c71e727d 100644 --- a/apps/drained/app.ts +++ b/apps/drained/app.ts @@ -151,6 +151,7 @@ Bangle.on("charging", charging => { drainedInterval = clearInterval(drainedInterval) as undefined; if(charging) drainedInterval = setInterval(checkCharge, interval * 60 * 1000); + draw(); // redraw to update charging status on screen }); if(!keepStartup){ diff --git a/apps/drained/metadata.json b/apps/drained/metadata.json index eff9a331b1..84addc8039 100644 --- a/apps/drained/metadata.json +++ b/apps/drained/metadata.json @@ -1,7 +1,7 @@ { "id": "drained", "name": "Drained", - "version": "0.07", + "version": "0.08", "description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals", "readme": "README.md", "icon": "icon.png", diff --git a/apps/followtherecipe/metadata.json b/apps/followtherecipe/metadata.json index 0c1de08178..59fabc8c5e 100644 --- a/apps/followtherecipe/metadata.json +++ b/apps/followtherecipe/metadata.json @@ -6,7 +6,7 @@ "version": "0.02", "description": "Follow The Recipe (FTR) is a bangle.js app to follow a recipe step by step", "type": "app", - "tags": "tool, tools, cook", + "tags": "tool,tools,cook", "supports": [ "BANGLEJS2" ], diff --git a/apps/gassist/metadata.json b/apps/gassist/metadata.json index 995c44adb8..bd38cdfb8b 100644 --- a/apps/gassist/metadata.json +++ b/apps/gassist/metadata.json @@ -5,7 +5,7 @@ "description": "A simple way to initiate Google Assistant on Android. Intents must be enabled in Gadgetbridge.", "icon": "app.png", "type": "app", - "tags": "tool, voice, tasker", + "tags": "tool,voice,tasker", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": false, "storage": [ diff --git a/apps/gbdiscon/metadata.json b/apps/gbdiscon/metadata.json index ecc92d01cf..904b4c6a22 100644 --- a/apps/gbdiscon/metadata.json +++ b/apps/gbdiscon/metadata.json @@ -4,7 +4,7 @@ "version":"0.01", "description": "Disconnect from your android device by running this app. The app will forward you to your clock face immediately after triggering the command. (Gadgetbridge nightly required until version 82 is released)", "icon": "app.png", - "tags": "android, gadgetbridge, bluetooth, bt", + "tags": "android,gadgetbridge,bluetooth,bt", "supports" : ["BANGLEJS", "BANGLEJS2"], "storage": [ {"name":"gbdiscon.app.js","url":"app.js"}, diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 0275542fb6..433c583689 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -12,3 +12,4 @@ 0.11: Use default Bangle formatter for booleans 0.12: Issue newline before GB commands (solves issue with console.log and ignored commands) 0.13: Upgrade to new translation system +0.14: Fix auto-start saved state; fix clearing track number; allow widget clicks diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 7f5aad8f37..31e56ec396 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -91,8 +91,7 @@ function rScroller(l) { const w = g.stringWidth(l.label)+40, y = l.y+l.h/2; l.offset = l.offset%w; - g.setClipRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) - .setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout + g.setColor(l.col).setBgColor(l.bgCol) // need to set colors: iScroll calls this function outside Layout .setFontAlign(-1, 0) // left center .clearRect(l.x, l.y, l.x+l.w-1, l.y+l.h-1) .drawString(l.label, l.x-l.offset+40, y) @@ -128,57 +127,8 @@ function rInfo(l) { .setFontAlign(0, -1) // center top .drawString(l.label, l.x+l.w/2, l.y); } -/** - * Render icon - * @param l - */ -function rIcon(l) { - const x2 = l.x+l.w-1, - y2 = l.y+l.h-1; - switch(l.icon) { - case "pause": { - const w13 = l.w/3; - g.drawRect(l.x, l.y, l.x+w13, y2); - g.drawRect(l.x+l.w-w13, l.y, x2, y2); - break; - } - case "play": { - g.drawPoly([ - l.x, l.y, - x2, l.y+l.h/2, - l.x, y2, - ], true); - break; - } - case "previous": { - const w15 = l.w*1/5; - g.drawPoly([ - x2, l.y, - l.x+w15, l.y+l.h/2, - x2, y2, - ], true); - g.drawRect(l.x, l.y, l.x+w15, y2); - break; - } - case "next": { - const w45 = l.w*4/5; - g.drawPoly([ - l.x, l.y, - l.x+w45, l.y+l.h/2, - l.x, y2, - ], true); - g.drawRect(l.x+w45, l.y, x2, y2); - break; - } - default: { // red X - console.log(`Unknown icon: ${l.icon}`); - g.setColor("#f00") - .drawRect(l.x, l.y, x2, y2) - .drawLine(l.x, l.y, x2, y2) - .drawLine(l.x, y2, x2, l.y); - } - } -} + + let layout; function makeUI() { Bangle.loadWidgets(); @@ -417,7 +367,7 @@ function handleButton2Press() { let tCommand = {}; /** * Send command and highlight corresponding control - * @param {string} command - "play"/"pause"/"next"/"previous"/"volumeup"/"volumedown" + * @param {"play"|"pause"|"playpause"|"next"|"previous"|"volumeup"|"volumedown"} command */ function sendCommand(command) { Bluetooth.println(""); @@ -433,15 +383,21 @@ function sendCommand(command) { drawControls(); } +function handleTouch(btn, pos) { + if (pos === undefined || pos.y >= Bangle.appRect.y) { + togglePlay(); + } +} + function togglePlay() { - sendCommand(stat==="play" ? "pause" : "play"); + sendCommand("playpause"); } /** * Setup touch+swipe for Bangle.js 1 */ function touch1() { - Bangle.on("touch", togglePlay); + Bangle.on("touch", handleTouch); Bangle.on("swipe", dir => { sendCommand(dir===1 ? "previous" : "next"); }); @@ -450,7 +406,7 @@ function touch1() { * Setup touch+swipe for Bangle.js 2 */ function touch2() { - Bangle.on("touch", togglePlay); + Bangle.on("touch", handleTouch); // swiping let drag; Bangle.on("drag", e => { @@ -483,10 +439,9 @@ function startLCDWatch() { Bangle.on("lcdPower", (on) => { if (on) { // redraw and resume scrolling - tick(); layout.render(); fadeOut(); - if (offset.offset!==null) { + if (layout.title.offset!==null) { // Making an assumption about what offset.offset was supposed to be if (!iScroll) { iScroll = setInterval(scroll, 200); } diff --git a/apps/gbmusic/boot.js b/apps/gbmusic/boot.js index 154f85c2b4..2f1c09d8ad 100644 --- a/apps/gbmusic/boot.js +++ b/apps/gbmusic/boot.js @@ -1,6 +1,6 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot.js GB () => { - const APP = global.__FILE__==="gbmusic.app.js", + const APP = globalThis.__FILE__==="gbmusic.app.js", a = !!(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart; let s, i; // state, info @@ -10,7 +10,7 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot. * Only runs while other apps are loaded */ function check() { - if (s!=="play" || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled + if ((!s || s.state!=="play") || !i || !a || !Bangle.CLOCK) return; // only launch app if we know which song we are playing, and autoLoad is enabled delete (i.t); // store info and launch music app require("Storage").writeJSON("gbmusic.load.json", { @@ -20,18 +20,19 @@ setTimeout( // make other boot code run first, so we override e.g. android.boot. load("gbmusic.app.js"); } - global.GB = (_GB => e => { + + globalThis.GB = (_GB => e => { // we eat music events! switch(e.t) { case "musicinfo": i = e; - return APP ? info(e) : check(); + return APP ? globalThis.info(e) : check(); case "musicstate": - s = e.state; - return APP ? state(e) : check(); + s = e; + return APP ? globalThis.state(e) : check(); default: // pass on other events if (_GB) setTimeout(_GB, 0, e); } - })(global.GB); + })(globalThis.GB); }, 1); diff --git a/apps/gbmusic/metadata.json b/apps/gbmusic/metadata.json index 0024a1708d..a463e6ea01 100644 --- a/apps/gbmusic/metadata.json +++ b/apps/gbmusic/metadata.json @@ -2,7 +2,7 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.13", + "version": "0.14", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", "screenshots": [{"url":"screenshot_v1_d.png"},{"url":"screenshot_v1_l.png"}, diff --git a/apps/golfview/metadata.json b/apps/golfview/metadata.json index bc3a00f901..cf77f3a264 100644 --- a/apps/golfview/metadata.json +++ b/apps/golfview/metadata.json @@ -3,9 +3,9 @@ "version": "0.03", "description": "This app will provide you with on course data to support your golf game!", "icon": "golfview.png", - "tags": "outdoors, gps", + "tags": "outdoors,gps", "allow_emulator": true, - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "storage": [ diff --git a/apps/grandfatherclock/ChangeLog b/apps/grandfatherclock/ChangeLog new file mode 100644 index 0000000000..a21dc397db --- /dev/null +++ b/apps/grandfatherclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: New Widget! +0.02: rename, new icon, settings menu! diff --git a/apps/grandfatherclock/README.md b/apps/grandfatherclock/README.md new file mode 100644 index 0000000000..b701164cc7 --- /dev/null +++ b/apps/grandfatherclock/README.md @@ -0,0 +1,26 @@ +# Grandfather Clock + +A widget that runs in the background and chimes on every (configurable) fraction of an hour, similar to Chimer, and counts out the fractions and the o'clock hour. + +## Usage + +Once installed, see the App Settings page for options. + +Defaults: +- Twelve hour mode is ENABLED. +- Swap meridian is DISABLED. (in the AM, there will be a single buzz after counting the hours. in the PM, there will be two buzzes after counting the hours) +- The attention buzz for the hour chime is 1000ms long. +- The buzz for each hour count is 250ms long. +- The buzz for each fraction count is 250ms long. +- The widget will count out 4 fractions of an hour (a 15 min interval). +- The time between count buzzes is 500ms. +- The meridian buzzes are 50ms long. +- The time between meridian buzzes is 300ms. + +## Requests + +Drop me a message at @yogsoy on Discord if you need help / discover a bug that I can squash for you. + +## Creator + +Written by June B (yogsoy), inspired by aaronrolls' Chimer. diff --git a/apps/grandfatherclock/icon.png b/apps/grandfatherclock/icon.png new file mode 100644 index 0000000000..8dc851c043 Binary files /dev/null and b/apps/grandfatherclock/icon.png differ diff --git a/apps/grandfatherclock/metadata.json b/apps/grandfatherclock/metadata.json new file mode 100644 index 0000000000..053e384d6b --- /dev/null +++ b/apps/grandfatherclock/metadata.json @@ -0,0 +1,18 @@ +{ "id": "grandfatherclock", + "name": "Grandfather Clock Widget", + "shortName":"Grandfather Clock", + "version":"0.02", + "description": "A widget that chimes every fraction of an hour (similar to Chimer), and counts out the fractions and the o'clock hour.", + "icon": "icon.png", + "type": "widget", + "tags": "widget", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"grandfatherclock.wid.js","url":"widget.js"}, + {"name":"grandfatherclock.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"grandfatherclock.json"} + ] +} diff --git a/apps/grandfatherclock/settings.js b/apps/grandfatherclock/settings.js new file mode 100644 index 0000000000..6401dffa5e --- /dev/null +++ b/apps/grandfatherclock/settings.js @@ -0,0 +1,89 @@ +(function(back) { + + const configFile = "grandfatherclock.json"; + + let config = Object.assign({ + draw_widget: true, + twelve_hour: true, + swap_meridian: false, + hour_attention_buzz_ms: 1000, + hour_count_buzz_ms: 250, + fraction_count_buzz_ms: 250, + fractions_of_hour: 4, // 4 = 15min intervals, 6 = 10min intervals + wait_ms: 500, + meridian_buzz_ms: 50, + meridian_buzz_wait_ms: 300 + }, require('Storage').readJSON("grandfatherclock.json", true) || {}); + + let writeConfig = function() { + require('Storage').writeJSON(configFile, config); + }; + + E.showMenu({ + "": {"title" : "Grandfather Clock"}, + "< Back": () => back(), + "Draw widget": { + value: config.draw_widget, + onchange: v => { + config.draw_widget = v; + writeConfig(); + } + }, + "12 hour": { + value: config.twelve_hour, + onchange: v => { + config.twelve_hour = v; + writeConfig(); + } + },"Swap meridian": { + value: config.swap_meridian, + onchange: v => { + config.swap_meridian = v; + writeConfig(); + } + },"Hr attn. buzz length (ms)": { + value: config.hour_attention_buzz_ms, + onchange: v => { + config.hour_attention_buzz_ms = v; + writeConfig(); + } + },"Hr count buzz (ms)": { + value: config.hour_count_buzz_ms, + onchange: v => { + config.hour_count_buzz_ms = v; + writeConfig(); + } + },"Frac. count buzz (ms)": { + value: config.fraction_count_buzz_ms, + onchange: v => { + config.fraction_count_buzz_ms = v; + writeConfig(); + } + },"Fracs. of hour": { + value: config.fractions_of_hour, + onchange: v => { + config.fractions_of_hour = v; + writeConfig(); + } + },"Count wait (ms)": { + value: config.wait_ms, + onchange: v => { + config.wait_ms = v; + writeConfig(); + } + },"Meridian buzz (ms)": { + value: config.meridian_buzz_ms, + onchange: v => { + config.meridian_buzz_ms = v; + writeConfig(); + } + },"Meridian wait (ms)": { + value: config.meridian_buzz_wait_ms, + onchange: v => { + config.meridian_buzz_wait_ms = v; + writeConfig(); + } + } + }); + +}) diff --git a/apps/grandfatherclock/widget.js b/apps/grandfatherclock/widget.js new file mode 100644 index 0000000000..96e121e631 --- /dev/null +++ b/apps/grandfatherclock/widget.js @@ -0,0 +1,81 @@ +(() => { + + // sensible defaults + let config = Object.assign({ + draw_widget: true, + twelve_hour: true, + swap_meridian: false, + hour_attention_buzz_ms: 1000, + hour_count_buzz_ms: 250, + fraction_count_buzz_ms: 250, + fractions_of_hour: 4, // 4 = 15min intervals, 6 = 10min intervals + wait_ms: 500, + meridian_buzz_ms: 50, + meridian_buzz_wait_ms: 300 + }, require('Storage').readJSON("grandfatherclock.json", true) || {}); // or, load the app settings file. + + WIDGETS["grandfatherclock"] = { + area: "tr", + width: config.draw_widget ? 16 : 0, + draw: function() { + if (config.draw_widget) { + g.reset(); + g.drawImage(atob("EBiDASSTJJISSSSZJJJCSSTJ///ISSZP///5CTJ/////ITJ/////ITJ/+B//ITJ/+B//ITJ//+P/ITJ/////ISZP///5CSRJ///ICSQJJJJACSYBJJIBCSYABgABCSYABgABCSYAJAABCSYANgABCSYBtgABCSYNtsABCSYBtgABCSYAMAABCSYAAAABCSZJJJJJCQ=="), this.x, this.y); + } + } + }; + + let date; + let fractionMs = 3600000 / config.fractions_of_hour; + + let chime = function () { + date = new Date(); + let hourFrac = Math.floor(date.getMinutes() / (60 / config.fractions_of_hour)); + + if (hourFrac == 0) { // if it's an o'clock hour + let chimeHour = (config.twelve_hour ? date.getHours() % 12 : date.getHours()); + if (chimeHour == 0) (config.twelve_hour ? chimeHour += 12 : chimeHour += 24); + + Bangle.buzz(config.hour_attention_buzz_ms).then(() => { // initial buzz + setTimeout(hourChime, config.wait_ms, chimeHour); // wait a period before doing the first chime + }); + } else { // if it's a fraction of an hour + fractionChime(hourFrac); + } + + queueNextChime(); + }; + + let hourChime = function (hoursLeftToChime) { + hoursLeftToChime--; + Bangle.buzz(config.hour_count_buzz_ms).then(() => { // recursive. buzz and wait to do the next buzz. + if (hoursLeftToChime > 0) { + setTimeout(hourChime, config.wait_ms, hoursLeftToChime); + } else if (config.twelve_hour) { // once finished with the hour count + setTimeout(meridianChime, config.wait_ms, (date.getHours() >= 12)); // if in twelve hour mode, queue up the meridian chime. + } + }); + }; + + let fractionChime = function (fractionsLeftToChime) { + fractionsLeftToChime--; + Bangle.buzz(config.fraction_count_buzz_ms).then(() => { // recursive. buzz and wait to do the next buzz. + if (fractionsLeftToChime > 0) setTimeout(fractionChime, config.wait_ms, fractionsLeftToChime); + }); + }; + + let meridianChime = function (meridian) { + if ((config.swap_meridian ? !meridian : meridian)) { // default: if PM + Bangle.buzz(config.meridian_buzz_ms).then(setTimeout(Bangle.buzz, config.meridian_buzz_wait_ms, config.meridian_buzz_ms)); // buzz once, wait, buzz again. + } else { // default: if AM + Bangle.buzz(config.meridian_buzz_ms); // buzz once. + } + }; + + let queueNextChime = function () { + let msUntilNextFraction = fractionMs - (Date.now() % fractionMs); + setTimeout(chime, msUntilNextFraction); + }; + + queueNextChime(); +})() diff --git a/apps/guitar/metadata.json b/apps/guitar/metadata.json index 0dc6893d6d..7b4683c472 100644 --- a/apps/guitar/metadata.json +++ b/apps/guitar/metadata.json @@ -4,8 +4,8 @@ "version": "0.03", "description": "Wrist mounted guitar chords", "icon": "app.png", - "tags": "guitar, chords", - "supports" : ["BANGLEJS2"], + "tags": "guitar,chords", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"guitar.app.js","url":"app.js"}, diff --git a/apps/guitarsongs/metadata.json b/apps/guitarsongs/metadata.json index 242850a703..18e1f7146c 100644 --- a/apps/guitarsongs/metadata.json +++ b/apps/guitarsongs/metadata.json @@ -5,7 +5,7 @@ "description": "Songs lyrics and guitar chords", "icon": "app.png", "screenshots": [{"url": "screenshot.png"}], - "tags": "guitar, song, lyrics, chords", + "tags": "guitar,song,lyrics,chords", "supports" : ["BANGLEJS2"], "interface": "manage_songs.html", "readme": "README.md", diff --git a/apps/icons/gen/generate.js b/apps/icons/gen/generate.js index b91eedcdcd..fd97a1ce9d 100755 --- a/apps/icons/gen/generate.js +++ b/apps/icons/gen/generate.js @@ -7,7 +7,7 @@ /* eslint-env node */ -var imageconverter = require("../../../webtools/imageconverter.js").imageconverter; +var imageconverter = require("../../../webtools/imageconverter.js"); var icons = JSON.parse(require("fs").readFileSync(__dirname+"/icon_names.json")); const imgOptions = { mode : "1bit", diff --git a/apps/intervalTimer/metadata.json b/apps/intervalTimer/metadata.json index 2722473c1c..a0489833ce 100644 --- a/apps/intervalTimer/metadata.json +++ b/apps/intervalTimer/metadata.json @@ -5,7 +5,7 @@ "icon": "app.png", "version":"0.01", "description": "Interval Timer for workouts, HIIT, or whatever else.", - "tags": "timer, interval, hiit, workout", + "tags": "timer,interval,hiit,workout", "readme":"README.md", "supports":["BANGLEJS2"], "storage": [ diff --git a/apps/kanagsec/metadata.json b/apps/kanagsec/metadata.json index c00ec9d7ad..2e3376c5b6 100644 --- a/apps/kanagsec/metadata.json +++ b/apps/kanagsec/metadata.json @@ -1,23 +1,23 @@ -{ +{ "id": "kanagsec", "name": "Kanagawa clock", "shortName":"kanagawa", "version": "0.04", "description": "A clock that displays the great wave of kanagawa (image from wikipedia) with seconds in active mode.", "icon": "app.png", - "tags": "clock, kanagawa, wave", + "tags": "clock,kanagawa,wave", "type": "clock", - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS2"], "readme": "README.md", "allow_emulator":true, - "storage": + "storage": [ {"name":"kanagsec.app.js","url":"app.js"}, {"name":"kanagsec.img","url":"app-icon.js","evaluate":true} ], - "screenshots" : + "screenshots" : [ { "url":"screenshot.png" }, - { "url":"screenshot2.png" } + { "url":"screenshot2.png" } ] } \ No newline at end of file diff --git a/apps/lint_exemptions.js b/apps/lint_exemptions.js index 1b3a3cc11b..70b5bbb388 100644 --- a/apps/lint_exemptions.js +++ b/apps/lint_exemptions.js @@ -79,12 +79,6 @@ module.exports = { "no-undef" ] }, - "apps/podadrem/app.js": { - "hash": "f7392e74974f69553aade2dcc07527b5d3b71a9b84e6dc5e0dd995e78dff8007", - "rules": [ - "no-unused-vars" - ] - }, "apps/openstmap/imagefilter.js": { "hash": "8a71e9a0ecfa94150379c41a5a915cfdac39f8b2e485e5cc1ce4629e85293acd", "rules": [ @@ -148,13 +142,6 @@ module.exports = { "no-unused-vars" ] }, - "apps/waypoints/waypoints.app.js": { - "hash": "ff02639eb8f81784c942093d57d254295e2c78918adb4bcffdf0d0284ebfac55", - "rules": [ - "no-unused-vars", - "no-undef" - ] - }, "apps/timerclk/timer.js": { "hash": "3b70606f1742b34bf4769f6e8703144ff0c3ba401a3a21f97ddcab5432c94db5", "rules": [ @@ -195,12 +182,6 @@ module.exports = { "no-unused-vars" ] }, - "apps/poweroff/settings.js": { - "hash": "c197afe72c612a4b3825a3a12a628d0f4ed83823da3f28885bbf473037a02506", - "rules": [ - "no-unused-vars" - ] - }, "apps/oxofocus/app.js": { "hash": "cd29309373974ef038725e26d8a2ff2634a437c89cdffc3b12defd65a948db75", "rules": [ @@ -208,13 +189,6 @@ module.exports = { "no-undef" ] }, - "apps/orloj/app.js": { - "hash": "fa43f035d759ba94f564392f410c0c6c485bb41a33c062f03d666738d517ccf3", - "rules": [ - "no-unused-vars", - "no-undef" - ] - }, "apps/nixie/m_vatch.js": { "hash": "36a7fa956f99d5815cd6bac570d2b86833d1d37474d7eef0bb21892bdf6723a8", "rules": [ @@ -490,13 +464,6 @@ module.exports = { "no-undef" ] }, - "apps/doztime/app-bangle1.js": { - "hash": "1e9598c201175180ae77d1c3bc47e8138b339b72eb58782b5057fb7aefdc88a1", - "rules": [ - "no-unused-vars", - "no-undef" - ] - }, "apps/carcrazy/app.js": { "hash": "fd9f98f4f9e24c63a8fa02918dceab4503edf88e06537922caf86e85884408f3", "rules": [ @@ -587,12 +554,6 @@ module.exports = { "no-undef" ] }, - "apps/swp2clk/settings.js": { - "hash": "2d9de7ffdf171f6e7b058741046c8a326cf9d5dba651f52fcdb523214b08d626", - "rules": [ - "no-undef" - ] - }, "apps/sunclock/app.js": { "hash": "54f49294816e06907003a71289a8b79641b1cd082044fb22bb4c4b80f6845459", "rules": [ @@ -617,12 +578,6 @@ module.exports = { "no-undef" ] }, - "apps/spotrem/app.js": { - "hash": "35f3ee5371acde28427fd01e191ff58108d3d9e4db1a221dca081b7a7b9c3c77", - "rules": [ - "no-undef" - ] - }, "apps/speedalt/app.js": { "hash": "2c8a2c0d7f60a8c25ee86e943f59df8fb5816ceac00e9e077220227d8885b133", "rules": [ @@ -761,12 +716,6 @@ module.exports = { "no-undef" ] }, - "apps/owmweather/boot.js": { - "hash": "10fe98e871a7df3338fc764d62ac4db65c20c44774aad112f5faf26c7497f78e", - "rules": [ - "no-undef" - ] - }, "apps/openwind/app.js": { "hash": "91bfda771bd5cc3127ede3b52f5f088b733ac9e7fc2d8dff4f1d732e66a8af47", "rules": [ @@ -917,12 +866,6 @@ module.exports = { "no-undef" ] }, - "apps/infoclk/settings.js": { - "hash": "56adc3eff3cbc04dd08238ed7e559416ebbc7736c872070c757d70bf5f31b440", - "rules": [ - "no-undef" - ] - }, "apps/infoclk/app.js": { "hash": "27cc368eb1be14658db7fd482e78c4bc7673e2a1c68bc3a1a5f7c7e29915ca88", "rules": [ @@ -971,12 +914,6 @@ module.exports = { "no-undef" ] }, - "apps/gpssetup/gpssetup.js": { - "hash": "a63f670f4adf04895e6837270fcf19bd56f0a6d049cfb8e3ad8928c283d1bfd3", - "rules": [ - "no-undef" - ] - }, "apps/glbasic/glbasic.app.js": { "hash": "7d12a030d6f0ef69a0e5a9783229fd49c0a6a06bf751e3ac562145d2ce8350e9", "rules": [ diff --git a/apps/locale/locales.js b/apps/locale/locales.js index c704e0f904..1e30812275 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -261,7 +261,7 @@ var locales = { ampm: { 0: "am", 1: "pm" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %B %Y", "1": "%d/%m/%Y" }, // 1 mars 2020 // 01/03/2020 - abmonth: "janv,févr,mars,avril,mai,juin,juil,août,sept,oct,nov,déc", + abmonth: "janv,févr,mars,avr,mai,juin,juil,août,sept,oct,nov,déc", month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", abday: "dim,lun,mar,mer,jeu,ven,sam", day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", @@ -423,7 +423,7 @@ var locales = { ampm: { 0: "am", 1: "pm" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20 - abmonth: "janv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.", + abmonth: "janv,févr,mars,avr,mai,juin,juil,août,sept,oct,nov,déc", month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", abday: "dim,lun,mar,mer,jeu,ven,sam", day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", @@ -452,7 +452,7 @@ var locales = { speed: "kmh", distance: { 0: "m", 1: "km" }, temperature: "°C", - ampm: { 0: "vorm", 1: " nachm" }, + ampm: { 0: "vorm", 1: "nachm" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%Y" }, // Sonntag, 1. März 2020 // 1.3.2020 abmonth: "Jan,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez", @@ -471,7 +471,7 @@ var locales = { ampm: { 0: "AM", 1: "PM" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A %d %B %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20 - abmonth: "janv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.", + abmonth: "janv,févr,mars,avr,mai,juin,juil,août,sept,oct,nov,déc", month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre", abday: "dim,lun,mar,mer,jeu,ven,sam", day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi", @@ -567,7 +567,7 @@ var locales = { ampm: { 0: "am", 1: "pm" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%A %d %B de %Y", "1": "%d/%m/%Y" }, // dimenge 1 de març de 2020 // 01/03/2020 - abmonth: "gen.,febr.,març,abril,mai,junh,julh,ago.,set.,oct.,nov.,dec.", + abmonth: "gen,febr,març,abril,mai,junh,julh,ago,set,oct,nov,dec", month: "genièr,febrièr,març,abril,mai,junh,julhet,agost,setembre,octòbre,novembre,decembre", abday: "dg,dl,dm,dc,dj,dv,ds", day: "dimenge,diluns,dimars,dimècres,dijòus,divendres,dissabte", @@ -612,10 +612,10 @@ var locales = { speed: "km/h", distance: { 0: "m", 1: "km" }, temperature: "°C", - ampm: { 0: "dop.", 1: "pop." }, + ampm: { 0: "dop", 1: "pop" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%-d. %b %Y", 1: "%-d.%-m.%Y" }, // "3. jan. 2020" // "3.1.2020"(short) - abmonth: "sij.,velj.,ožu.,tra.,svi,lip.,srp.,kol.,ruj.,lis.,stu.,pro.", + abmonth: "sij,velj,ožu,tra,svi,lip,srp,kol,ruj,lis,stu,pro", month: "siječanj,veljača,ožujak,travanj,svibanj,lipanj,srpanj,kolovoz,rujan,listopad,studeni,prosinac", abday: "ned.,pon.,uto.,sri.,čet.,pet.,sub.", day: "nedjelja,ponedjeljak,utorak,srijeda,četvrtak,petak,subota", @@ -628,7 +628,7 @@ var locales = { speed: "km/h", distance: { 0: "m", 1: "km" }, temperature: "°C", - ampm: { 0: "dop.", 1: "pop." }, + ampm: { 0: "dop", 1: "pop" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%-d. %b %Y", 1: "%-d.%-m.%Y" }, // "3. jan. 2020" // "3.1.2020"(short) abmonth: "jan.,feb.,mar.,apr.,maj,jun.,jul.,avg.,sep.,okt.,nov.,dec.", @@ -728,7 +728,7 @@ var locales = { ampm: { 0: "am", 1: "pm" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, datePattern: { 0: "%d %B %Y", "1": "%d/%m/%y" }, - abmonth: "gen.,febr.,març,abr.,maig,juny,jul.,ag.,set.,oct.,nov.,des.", + abmonth: "gen,febr,març,abr,maig,juny,jul,ag,set,oct,nov,des", month: "gener,febrer,març,abril,maig,juny,juliol,agost,setembre,octubre,novembre,desembre", abday: "dg.,dl.,dt.,dc.,dj.,dv.,ds.", day: "diumenge,dilluns,dimarts,dimecres,dijous,divendres,dissabte", diff --git a/apps/messagesoverlay/metadata.json b/apps/messagesoverlay/metadata.json index 26095eb33a..ba5f005c3c 100644 --- a/apps/messagesoverlay/metadata.json +++ b/apps/messagesoverlay/metadata.json @@ -15,6 +15,6 @@ {"name":"messagesoverlay.settings.js","url":"settings.js"}, {"name":"messagesoverlay.default.json","url":"default.json"} ], - "data": [{"name":"bthrm.json"}], + "data":[{"name":"messagesoverlay.json"}], "screenshots": [{"url":"screen_call.png"} ,{"url":"screen_message.png"} ] } diff --git a/apps/msgtwscr/ChangeLog b/apps/msgtwscr/ChangeLog new file mode 100644 index 0000000000..5560f00bce --- /dev/null +++ b/apps/msgtwscr/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/msgtwscr/README.md b/apps/msgtwscr/README.md new file mode 100644 index 0000000000..2fbdda2402 --- /dev/null +++ b/apps/msgtwscr/README.md @@ -0,0 +1,19 @@ +# Message Twist to Scroll + +Temporarily activate scroll on twist function when a new message triggers the message app. This way it's possible to scroll through a message in the message scroller hands free. + +## Usage + +This is a bootloader app and only needs to be installed to add the functionality to the watch. + +## Notes + +Tried with [a development version "Message UI" app](https://github.com/espruino/BangleApps/pull/3685) centered around the message scroller. Should work with the standard version as well. + +## Requests + +Mention @thyttan in an issue on the espruino/BangleApps repository. + +## Creator + +thyttan diff --git a/apps/msgtwscr/app.png b/apps/msgtwscr/app.png new file mode 100644 index 0000000000..83d7e9add5 Binary files /dev/null and b/apps/msgtwscr/app.png differ diff --git a/apps/msgtwscr/boot.js b/apps/msgtwscr/boot.js new file mode 100644 index 0000000000..22bc03bccf --- /dev/null +++ b/apps/msgtwscr/boot.js @@ -0,0 +1,33 @@ +{ + // If doing regular loads, not Bangle.load, this is used: + if (global.__FILE__=="messagegui.new.js") { + onTwistEmitDrag(); + } + + // If Fastload Utils is installed this is used: + Bangle.on("message", (_, msg)=>{if (Bangle.CLOCK && msg.new) { + setTimeout(()=>{ + if (global.__FILE__=="messagegui.new.js") { + onTwistEmitDrag(); + } + },700) // It feels like there's a more elegant solution than checking the filename after 700 milliseconds. But this at least seems to work w/o sometimes activating when it shouldn't. + }}); + + // twistThreshold How much acceleration to register a twist of the watch strap? Can be negative for opposite direction. default = 800 + // twistMaxY Maximum acceleration in Y to trigger a twist (low Y means watch is facing the right way up). default = -800 + // twistTimeout How little time (in ms) must a twist take from low->high acceleration? default = 1000 + function onTwistEmitDrag() { + Bangle.setOptions({twistThreshold:2500, twistMaxY:-800, twistTimeout:400}); + Bangle.on("twist", ()=>{ + Bangle.setLocked(false); + Bangle.setLCDPower(true); + let i = 25; + const int = setInterval(() => { + Bangle.emit("drag", {dy:-3}) + i--; + if (i<1) clearInterval(int); + }, 10); + }); + + } +} diff --git a/apps/msgtwscr/metadata.json b/apps/msgtwscr/metadata.json new file mode 100644 index 0000000000..6f267ea011 --- /dev/null +++ b/apps/msgtwscr/metadata.json @@ -0,0 +1,13 @@ +{ "id": "msgtwscr", + "name": "Message Twist to Scroll", + "version":"0.01", + "description": "Temporarily activate scroll on twist function when a new message triggers the message app.", + "icon": "app.png", + "tags": "messages,tweak,scroll", + "type": "bootloader", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"msgtwscr.boot.js","url":"boot.js"} + ] +} diff --git a/apps/multidice/ChangeLog b/apps/multidice/ChangeLog index cb0cce2aab..ca17e18335 100644 --- a/apps/multidice/ChangeLog +++ b/apps/multidice/ChangeLog @@ -30,3 +30,8 @@ 1.28: increased vibration strength, added some comments, & some QOL 1.29: changed image 1.30: changed image, again +1.40: added various settings for controlling when & how to throw dice +1.41: fixed dumb mistake +1.42: okay maby I should've read the *whole* error log +1.43: playing whackamole with ESLint +1.44: fixed (?) settings app diff --git a/apps/multidice/app.js b/apps/multidice/app.js index 53f67e21e7..fe869626ae 100644 --- a/apps/multidice/app.js +++ b/apps/multidice/app.js @@ -1,12 +1,20 @@ -var menu = true; // default to have the selection menu open +var settings = Object.assign({ + vibrate: true, + shake: true, + screen: false, + shake_timeout: 200, + shake_duration: 100, +}, require('Storage').readJSON("multidice.json", true) || {}); + +var menu = true; // defaults to having the menu open const DICE_ARRAY = [0, 4, 6, 8, 10, 12, 20, 100]; // 0 means nothing selected const SELECTION_ARRAY = [6, 0, 0, 0, 0, 0, 0, 0]; // default to selecting a single d20 // function to draw the selection menu function drawMenu() { - stringArr = new Array ("", "", "", "", "", "", "", ""); - for (i = 0; i < 8; i++) { + var stringArr = new Array ("", "", "", "", "", "", "", ""); + for (var i = 0; i < 8; i++) { if (SELECTION_ARRAY [i] != 0) { @@ -41,6 +49,7 @@ function touchHandler (button, xy) { return; } + var selection; if (xy.x <= 87) { // left if (xy.y <= 43) { // first @@ -84,15 +93,30 @@ function touchHandler (button, xy) { drawMenu(); } +var shaken = false; +var last_shaken = null; function accelHandler (xyz) { + // if the screen should be on *and* it isn't, return + if (settings.screen && ! Bangle.isBacklightOn()) { + + return; + } + if (xyz.diff >= 0.3) { - menu = false; - mutex (rollDice).catch (() => { + shaken = true; + last_shaken = Date.now(); + } else if (shaken && last_shaken !== null) { + + if (Date.now() - last_shaken > settings.shake_timeout) { - return; // not necessary, but prevents spamming the logs - }); + last_shaken = null; + shaken = false; + menu = false; + + mutex (rollDice).catch (() => { return; }); + } } } @@ -123,8 +147,8 @@ function mutex (functionRef) { // function to roll all selected dice, and display them function rollDice() { - resultsArr = new Uint8Array (8); - for (i = 0; i < 8; i++) { + var resultsArr = new Uint8Array (8); + for (var i = 0; i < 8; i++) { if (SELECTION_ARRAY [i] != 0) { @@ -135,7 +159,7 @@ function rollDice() { g.clear(); g.setFont ("Vector", 40); - for (i = 0; i < 4; i++) { + for (var i = 0; i < 4; i++) { if (SELECTION_ARRAY [i] != 0) { @@ -143,7 +167,7 @@ function rollDice() { } } - for (i = 4; i < 8; i++) { + for (var i = 4; i < 8; i++) { if (SELECTION_ARRAY [i] != 0) { @@ -157,14 +181,19 @@ function rollDice() { // triggers the vibration, then pauses before returning function vibrate() { + if (! settings.vibrate) { + + return (Promise.resolve (0)); + } + return new Promise ((resolve, reject) => { - return Bangle.buzz (50, 1).then ((value) => { + return Bangle.buzz (settings.shake_duration, 1).then ((value) => { setTimeout (() => { resolve (value); - }, 200); + }, 2 * settings.shake_duration + settings.shake_timeout); }); }); } @@ -177,7 +206,7 @@ function random (max) { drawMenu(); Bangle.on ('touch', touchHandler); -Bangle.on ('accel', accelHandler); +if (settings.shake) { Bangle.on ('accel', accelHandler); } setWatch (function() { menu = false; diff --git a/apps/multidice/metadata.json b/apps/multidice/metadata.json index 304c789e45..dd69410608 100644 --- a/apps/multidice/metadata.json +++ b/apps/multidice/metadata.json @@ -1,7 +1,7 @@ { "id": "multidice", "name": "multiple dice roller", "shortName":"multidice", - "version":"1.30", + "version":"1.44", "description": "roll anywhere from 1-8 dice at the same time", "icon": "app.png", "tags": "tool,game", @@ -10,6 +10,8 @@ "allow_emulator": true, "storage": [ {"name":"multidice.app.js","url":"app.js"}, + {"name":"multidice.settings.js","url":"settings.js"}, {"name":"multidice.img","url":"app-icon.js","evaluate":true} - ] + ], + "data": [{"name": "multidice.json"}] } diff --git a/apps/multidice/settings.js b/apps/multidice/settings.js new file mode 100644 index 0000000000..446a785e10 --- /dev/null +++ b/apps/multidice/settings.js @@ -0,0 +1,58 @@ +(function(back) { + var settings = Object.assign({ + vibrate: true, + shake: true, + screen: false, + shake_timeout: 200, + shake_duration: 100, + }, require('Storage').readJSON("multidice.json", true) || {}); + + function writeSettings() { + require('Storage').writeJSON("multidice.json", settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "multi dice roll" }, + "< Back" : () => back(), + 'vibrate on roll?': { + value: !!settings.vibrate, + onchange: v => { + settings.vibrate = v; + writeSettings(); + } + }, + 'allow shaking?': { + value: !!settings.shake, + onchange: v => { + settings.shake = v; + writeSettings(); + } + }, + 'screen on to shake?': { + value: !!settings.screen, + onchange: v => { + settings.screen = v; + writeSettings(); + } + }, + 'shake timeout': { + value: settings.shake_timeout / 5, + min: 10, max: 40, + format: v => v * 5, + onchange: v => { + settings.shake_timeout = v * 5; + writeSettings(); + } + }, + 'shake duration': { + value: settings.shake_duration / 5, + min: 10, max: 40, + format: v => v * 5, + onchange: v => { + settings.shake_duration = v * 5; + writeSettings(); + } + }, + }); +}) diff --git a/apps/ohmcalc/metadata.json b/apps/ohmcalc/metadata.json index c72727a4cc..2ab25947f3 100644 --- a/apps/ohmcalc/metadata.json +++ b/apps/ohmcalc/metadata.json @@ -6,7 +6,7 @@ "description": "A smart and simple calculator for Ohm's Law calculations, designed specifically for Bangle.js 2 smartwatches. Handles voltage, current, resistance, and power calculations with smart logic to prevent invalid inputs.", "icon": "app.png", "type": "app", - "tags": "calculator, utilities, electric", + "tags": "calculator,utilities,electric", "supports": ["BANGLEJS2"], "readme": "README.md", "allow_emulator": true, diff --git a/apps/pacer/README.md b/apps/pacer/README.md new file mode 100644 index 0000000000..0824bc7f4d --- /dev/null +++ b/apps/pacer/README.md @@ -0,0 +1,56 @@ +## Pacer + + + +Run with a virtual partner at your chosen pace, and export the GPX data +from the Bangle.js App Store. + +## Usage + +Pacer starts up with a menu. + +* **Recording** - whether to record the run +* **Units** - imperial or metric +* **Lap** - the multiple of a mile or kilometer to use for splits +* **Dark mode** - use black or white background +* **Eco battery** - display will turn off after 10 seconds +* **Eco storage** - only record GPS position every 10 seconds +* **Steps** - display step count or cadence +* **Pacer** - pace of virtual partner + +On selecting **Start**, GPS position will be detected. A run cannot be +started without a GPS fix. The watch touchscreen is disabled while the +app is running. + +The app will run on Bangle.js 1 and 2, although use on Bangle.js 2 is not +recommended due to poor GPS accuracy. + +On a Bangle.js 1, the top button reverses the screen colours, the middle +button starts, pauses or resumes a run, and the bottom button ends the run. + +On a Bangle.js 2, a short press of the button starts, pauses or resumes a +run, and a long press (over 0.5 seconds, but under 2!) ends the run. Note +that holding the button for 2 seconds will exit back to the default clock +app. + +## Downloading + +GPX tracks can be downloaded using the +[App Loader](https://banglejs.com/apps/?id=pacer). Connect the +Bangle.js and click on the Pacer app's disk icon to see the tracks +available for downloading. + +## Tips + +For best results, only start a run when the satellite signal strength bar is +green. + +Use the [Assisted GPS Updater](https://banglejs.com/apps/#AGPS) to improve +the time taken to get a GPS fix. + +## Bugs + +The eco settings are unlikely to be useful. + +GPS track smoothing is accomplished simply by reducing the frequency with +which readings are taken, depending on signal strength. diff --git a/apps/pacer/app-icon.js b/apps/pacer/app-icon.js new file mode 100644 index 0000000000..33eeed26bc --- /dev/null +++ b/apps/pacer/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///un/+2Eqee1nV+X26NjtfNGLsf+AQOg+kzAROnskyfACJs5CINACJshCKAjMgfJBoPwgpHLiGSpMk3kHyneO5OECIMl23Aj+AO5QRD7ZSJgPpknZkmNCIJFJhFJk1wgFtCIN4CJFCpMwAgIRCt5HIoVN8B/BCIVmEZFyt6RCCIb7Ih5SCmYRCkjoM7YRB78k6ARO++kzwQKhgRC4GEzARKswRD8mT///Lg8DtmbCILvEEw8DkmzCIUcCIWTN40bkmWUoUCCIWbCJGSrIpC5YoB5x7HCINLRZcAg3bsgwBsARNtgRBlgRLmyNBCIMlwAQJgLkBtuyMwIRHvJUBpP2GoMGCIOwCI1SBQOSr3bA4PJSQKtGSoWSpXYBAMZAwKcGoQRCpoLCZAOSoARFh5HCk4IDtmpnikMAAMOpO8CJ0KpKQKAAlyfoQANqVMCByGBt4ROgWSfhgACjmTvAROimTCB0A8mbYgwAIwmYEZ/07wRPj/wCJ4AI")) diff --git a/apps/pacer/app.js b/apps/pacer/app.js new file mode 100644 index 0000000000..6b2b9efcef --- /dev/null +++ b/apps/pacer/app.js @@ -0,0 +1,843 @@ +Bangle.loadWidgets(); +g.clear(); +Bangle.drawWidgets(); + +var cfg; +try { + cfg = require("Storage").readJSON("pacer.json",true)||{}; +} +catch(err) { + cfg = {}; +} +if (process.env.BOARD == 'BANGLEJS2') + var bangle2 = true; +else + var bangle2 = false; + +var laps = ["Off","0.25","0.5","1","2","5","10"]; +var fg = 1; +var fixed = false; +var started = false; +var startHidden = false; +var recording = false; +var invID = 0; +var intID = 0; +var startID = 0; +var cadID = 0; +var finID = 0; +var lapID = 0; +var steps = 0; +var sats = 0; +var ctr = 0; +var elapsed_ms = 0; +var finish_ms = 0; +var lap_start_ms = 0; +var lap_ms; +var gps = {fix:0,satellites:0}; +var fp; +var start_time; +var current_time; +var paused_time = 0; +var last_time = 0; +var begin_pause; +var next_lap = 0.0; +var skip_ctr = 0; +var skip_max = 0; +var force_write = true; +var show_lap = false; +var lcd_on = true; +var drawSats = true; +var dist = 0.0; +var pdist = 0.0; +var oldDist = 0.0; +var oldLat = -1; +var oldLon = -1; +var cadence = 0; +var pace = 0; +var ppace = 0; +var R = 6371; +var stepTimes = []; +var dists = []; + +function pace_str(pval) { + var psecs = 295 + 5 * pval; + return ''+Math.floor(psecs/60)+':'+('0'+psecs%60).substr(-2); +} + +function defaults() { + if (typeof(cfg.record) != 'boolean') + cfg.record = true; + if (typeof(cfg.metric) != 'boolean') + cfg.metric = false; + if (typeof(cfg.lap_idx) != 'number') + cfg.lap_idx = 3; + if (typeof(cfg.dark) != 'boolean') + cfg.dark = true; + if (typeof(cfg.eco) != 'boolean') + cfg.eco = false; + if (typeof(cfg.storage) != 'boolean') + cfg.storage = false; + if (typeof(cfg.show_steps) != 'boolean') + cfg.show_steps = false; + if (typeof(cfg.pacer) != 'number') + cfg.pacer = 0; + fg = cfg.dark?1:0; +} + +function genFilename() { + var today=new Date(); + return ('.pacer'+today.getFullYear()+('0'+(today.getMonth()+1)).substr(-2)+('0'+today.getDate()).substr(-2)+('0'+today.getHours()).substr(-2)+('0'+today.getMinutes()).substr(-2)+('0'+today.getSeconds()).substr(-2)+'.csv'); +} + +function doCadence() { + if (steps > 0) + clearInterval(cadID); + cadID = setTimeout(function() { + cadence = 0; + }, 2000); + if (recording) { + steps++; + stepTimes.push(Date.now()); + stepTimes = stepTimes.slice(-20); + const elapsed = stepTimes[stepTimes.length - 1] - stepTimes[0]; + cadence = elapsed ? Math.round(60000 * (stepTimes.length - 1) / elapsed) : 0; + } else + stepTimes = []; +} + +function doPace(thistime,thisdist) { + dists.push([thistime,thisdist]); + dists = dists.slice(-30); + const thiselapsed = dists[dists.length - 1][0] - dists[0][0]; + const thisdistance = dists[dists.length - 1][1] - dists[0][1]; + pace = thisdistance ? ((thiselapsed) / thisdistance) / 1000 : 0; +} + +function countStep() { + if (recording) + steps++; +} + +function calcCrow(lat1, lon1, lat2, lon2) +{ + var dLat = toRad(lat2-lat1); + var dLon = toRad(lon2-lon1); + var lat1r = toRad(lat1); + var lat2r = toRad(lat2); + + var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1r) * Math.cos(lat2r); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + var d = R * c; + if (isNaN(d)) + return 0; + else + return d; +} + +function toRad(Value) +{ + return Value * Math.PI / 180; +} + +function saveGPS(fix) { + var newLat, newLon, newTime, newDist; + + try { + newTime = fix.time.getTime(); + } + catch(err) { + newTime = NaN; + } + newLat = fix.lat; + newLon = fix.lon; + gps = fix; + if (!cfg.storage) { + if (gps.satellites >= 8) + skip_max = 0; + else if (gps.satellites < 4) + skip_max = 5; + else + skip_max = 8 - gps.satellites; + } + if (recording && cfg.pacer > 0 && skip_ctr >= (cfg.storage ? 9 : skip_max)) + pdist = (elapsed_ms / 1000) / ppace; + if (isNaN(newLat) || isNaN(newLon) || isNaN(newTime)) { + skip_ctr = 0; + skip_max = 0; + force_write = true; + } else { + if (oldLat != -1 && recording) { + skip_ctr++; + if (skip_ctr > (cfg.storage ? 9 : skip_max)) { + skip_ctr = 0; + oldDist = dist; + newDist = calcCrow(oldLat, oldLon, newLat, newLon); + dist += newDist; + doPace(newTime,dist); + oldLat = newLat; + oldLon = newLon; + } + } else { + oldLat = newLat; + oldLon = newLon; + } + if (recording && cfg.record && (force_write || skip_ctr == 0)) { + fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); + last_time = gps.time; + if (force_write) { + skip_ctr = 0; + force_write = false; + } + } + } +} + +function drawInvert() { + // not applicable to bangle2 + g.drawImage(atob("DQ0BD4HjHwT4L8D+B/A/gfwL4J8EeMD4AA=="),225,26); +} + +function drawSatIcon() { + if (bangle2) + g.drawImage(atob("CQkBIDo7i+Pj6O4uAgA="),3,53); + else + g.drawImage(atob("DAwBEAOAcQ84T8D4HwPyHPCOAcAI"),4,66); +} + +function drawStepsIcon() { + if (bangle2) + g.drawimage(atob("CQkBBhudzudzgBzsMAA="),3,139); + else + g.drawImage(atob("DAwBAMAcMeOeeeeeeAecAcOYOAGA"),4,197); +} + +function drawCadenceIcon() { + if (bangle2) + g.drawImage(atob("CQkBCB4SEBgMBCQ8CAA="),3,139); + else + g.drawImage(atob("DAwBBAAwP4YxxDwDwDwjjGH8DAAg"),4,197); +} + +function hideStart() { + g.clearRect(bangle2?162:226,bangle2?81:113,bangle2?174:238,bangle2?93:125); +} + +function drawStart() { + hideStart(); + g.fillPoly([bangle2?162:226,bangle2?81:113,bangle2?162:226,bangle2?93:125,bangle2?174:238,bangle2?87:119,bangle2?162:226,bangle2?81:113]); +} + +function drawPause() { + hideStart(); + g.fillRect(bangle2?165:227,bangle2?82:113,bangle2?167:230,bangle2?92:125); + g.fillRect(bangle2?171:234,bangle2?82:113,bangle2?173:237,bangle2?92:125); +} + +function drawStop() { + // not applicable to bangle2 + g.fillRect(226,202,237,213); +} + +function drawExit() { + if (bangle2) + g.drawImage(atob("CQkBwfHdx8HB8d3HwQg="),165,82); + else + g.drawImage(atob("DAwBwD4HcOOcH4DwDwH4OccO4HwD"),226,202); +} + +function setColours() { + g.setBgColor(1-fg,1-fg,1-fg); + g.setColor(fg,fg,fg); +} + +function setScreenMode() { + g.reset(); + setColours(); + g.clearRect(0,24,bangle2?175:239,bangle2?151:215); +} + +function doLayout() { + setColours(); + if (!bangle2) + drawInvert(); + drawSatIcon(); + drawDist(); + drawTime(); + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } + g.setFont("6x8",bangle2?2:3); + if (cfg.show_steps) { + drawStepsIcon(); + g.drawString(steps.toString(),bangle2?15:20,bangle2?134:190,true); + } else { + drawCadenceIcon(); + g.drawString(cadence.toString()+" ",bangle2?15:20,bangle2?134:190,true); + } + drawStart(); + if (!bangle2) + drawStop(); +} + +function drawDist() { + g.setFont("6x8",bangle2?4:5); // 3:5? + var dStr = dist.toString(); + if (dStr.indexOf('.') == -1) + dStr += '.0'; + g.drawString(((' '+(dStr.split('.'))[0])).substr(-2),bangle2?33:53,bangle2?26:35,true); + g.fillRect(bangle2?80:112,bangle2?51:66,bangle2?82:115,bangle2?53:69); + g.drawString(((dStr.split('.'))[1]+'0').substr(0,2),bangle2?86:120,bangle2?26:35,true); + g.setFont("6x8",2); + g.drawString(cfg.metric?"K":"M",bangle2?134:180,bangle2?40:56,true); +} + +function drawPacer() { + g.setFont("6x8",bangle2?3:5); + var pstr=(pdist>dist?'-':'+')+(Math.floor(Math.abs(dist-pdist)))%100; + g.drawString(pstr,bangle2?(49-(pstr.length>2?18:0)):(53-(pstr.length>2?30:0)),bangle2?107:145,true); + g.fillRect(bangle2?84:112,bangle2?126:176,bangle2?85:115,bangle2?127:179); + g.drawString(('0'+Math.floor(Math.abs(((dist-pdist)*100)%100))).substr(-2),bangle2?89:120,bangle2?107:145,true); + g.setFont("6x8",bangle2?1:2); + g.drawString(cfg.metric?"K":"M",bangle2?125:180,bangle2?121:166,true); +} + +function drawPace() { + g.setFont("6x8",bangle2?3:5); + if (pace > 0 && pace < 6000) + g.drawString((' '+Math.floor(pace/60)).substr(-2),bangle2?49:53,bangle2?107:145,true); + else + g.drawString("--",bangle2?49:53,bangle2?107:145,true); + g.fillRect(bangle2?84:112,bangle2?117:160,bangle2?85:115,bangle2?118:163); + g.fillRect(bangle2?84:112,bangle2?120:166,bangle2?85:115,bangle2?121:169); + if (pace > 0 && pace < 6000) + g.drawString(('0'+Math.floor(pace%60)).substr(-2),bangle2?89:120,bangle2?107:145,true); + else + g.drawString("--",bangle2?89:120,bangle2?107:145,true); + g.setFont("6x8",bangle2?1:2); + g.drawString(cfg.metric?"/K":"/M",bangle2?124:178,bangle2?121:166,true); +} + +function drawSmallPace() { + g.setFont("6x8",bangle2?2:3); + if (pace > 0 && pace < 6000) + g.drawString((' '+Math.floor(pace/60)).substr(-2),bangle2?113:136,bangle2?134:190,true); + else + g.drawString("--",bangle2?113:136,bangle2?134:190,true); + if (bangle2) { + g.setPixel(136,140); + g.setPixel(136,142); + } else { + g.fillRect(172,199,173,200); + g.fillRect(172,203,173,204); + } + if (pace > 0 && pace < 6000) + g.drawString(('0'+Math.floor(pace%60)).substr(-2),bangle2?138:176,bangle2?134:190,true); + else + g.drawString("--",bangle2?138:176,bangle2?134:190,true); + g.setFont("6x8",1); + g.drawString(cfg.metric?"/K":"/M",bangle2?164:212,bangle2?141:204,true); +} + +function drawTime() { + var seconds; + var minutes; + var hours; + + setColours(); + g.setFont("6x8",bangle2?5:7); + seconds = parseInt(elapsed_ms/1000) % 60; + minutes = parseInt(elapsed_ms/60000) % 60; + hours = parseInt(elapsed_ms/3600000) % 10; + g.drawString(hours.toString(),bangle2?6:5,bangle2?63:82,true); + g.fillRect(bangle2?34:44,bangle2?79:103,bangle2?36:48,bangle2?81:107); + g.fillRect(bangle2?34:44,bangle2?84:112,bangle2?36:48,bangle2?86:116); + g.drawString(('0'+minutes).substr(-2),bangle2?40:53,bangle2?63:82,true); + g.fillRect(bangle2?98:134,bangle2?79:103,bangle2?100:138,bangle2?81:107); + g.fillRect(bangle2?98:134,bangle2?84:112,bangle2?100:138,bangle2?86:116); + g.drawString(('0'+seconds).substr(-2),bangle2?104:143,bangle2?63:82,true); +} + +function drawGPSBox() { + g.drawRect(2,26,bangle2?12:17,bangle2?51:63); +} + +function drawGPS() { + g.clearRect(3,27,bangle2?11:16,bangle2?50:62); + if (gps.satellites > 0) { + if (!gps.fix) + g.setColor("#FF0000"); + else if (gps.satellites < 4) + g.setColor("#FF5500"); + else if (gps.satellites < 6) + g.setColor("#FF8800"); + else if (gps.satellites < 8) + g.setColor("#FFCC00"); + else + g.setColor("#00FF00"); + g.fillRect(3,bangle2?50:62,bangle2?11:16,(bangle2?50:62)-(gps.satellites>12?12:gps.satellites)*(bangle2?2:3)+1); + g.setColor(fg,fg,fg); + } +} + +function hideLap() { + show_lap = false; + g.reset(); + setColours(); + g.clearRect(bangle2?6:5,bangle2?63:82,bangle2?162:224,bangle2?129:182); + if (recording) { + current_time = Date.now(); + elapsed_ms = current_time - (start_time + paused_time); + } else + elapsed_ms = begin_pause - (start_time + paused_time); + drawTime(); + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } +} + +function showLap() { + g.clearRect(bangle2?6:5,bangle2?63:82,bangle2?162:224,bangle2?129:182); + g.drawRect(bangle2?21:28,bangle2?68:90,bangle2?147:201,bangle2?124:174); + g.drawRect(bangle2?23:30,bangle2?70:92,bangle2?145:199,bangle2?122:172); + g.setFont("6x8",bangle2?1:2); + g.drawString("Last lap",bangle2?61:68,bangle2?77:102,true); + g.setFont("6x8",bangle2?3:5); + if (lap_ms < 600000) { + g.drawString((''+Math.floor(lap_ms/60000)),bangle2?57:69,bangle2?89:122,true); + g.fillRect(bangle2?74:98,bangle2?99:137,bangle2?75:101,bangle2?100:140); + g.fillRect(bangle2?74:98,bangle2?102:143,bangle2?75:101,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%60000)/1000)).substr(-2),bangle2?78:106,bangle2?89:122,true); + } else if (lap_ms < 3600000) { + g.drawString((''+Math.floor(lap_ms/60000)),bangle2?48:54,bangle2?89:122,true); + g.fillRect(bangle2?83:113,bangle2?99:137,bangle2?84:116,bangle2?100:140); + g.fillRect(bangle2?83:113,bangle2?102:143,bangle2?84:116,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%60000)/1000)).substr(-2),bangle2?87:121,bangle2?89:122,true); + } else { + g.drawString((''+Math.floor(lap_ms/3600000)).substr(-1),bangle2?37:35,bangle2?89:122,true); + g.fillRect(bangle2?54:64,bangle2?99:137,bangle2?55:67,bangle2?100:140); + g.fillRect(bangle2?54:64,bangle2?102:143,bangle2?55:67,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%3600000)/60000)).substr(-2),bangle2?58:72,bangle2?89:122,true); + g.fillRect(bangle2?93:131,bangle2?99:137,bangle2?94:134,bangle2?100:140); + g.fillRect(bangle2?93:131,bangle2?102:143,bangle2?94:134,bangle2?103:146); + g.drawString(('0'+Math.floor((lap_ms%60000)/1000)).substr(-2),bangle2?97:139,bangle2?89:122,true); + } + Bangle.setLCDPower(true); +} + +function mainLoop() { + g.reset(); + setColours(); + current_time = Date.now(); + if (started) { + elapsed_ms = current_time - (start_time + paused_time); + if (oldDist != dist) { + drawDist(); + if (cfg.lap_idx > 0 && dist >= next_lap ) { + show_lap = true; + next_lap += parseFloat(laps[cfg.lap_idx]); + lap_ms = elapsed_ms - lap_start_ms; + lap_start_ms = elapsed_ms; + Bangle.buzz(); + lapID = setTimeout(hideLap,5000); + showLap(); + } + } + } else + elapsed_ms = 0; + drawSats = false; + if (recording) { + if (!show_lap) + drawTime(); + g.setFont("6x8",bangle2?2:3); + if (cfg.show_steps) + g.drawString(steps.toString(),bangle2?15:20,bangle2?134:190,true); + else + g.drawString(cadence.toString()+" ",bangle2?15:20,bangle2?134:190,true); + } /* else + g.setFont("6x8",3); */ + if (!show_lap) + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } + if (gps.fix) { + if (!started && startHidden) { + startHidden = false; + if (!bangle2) + startID = setWatch(start, BTN2); + else + startID = setWatch(start, BTN1, {edge: 'falling'}); + drawStart(); + } + if (!fixed) { + fixed = true; + drawSats = true; + } + } else { + if (!started && !startHidden) { + startHidden = true; + clearWatch(startID); + hideStart(); + } + if (fixed) { + fixed = false; + drawSats = true; + } + } + if (gps.satellites != sats) { + sats = gps.satellites; + drawSats = true; + } + if (drawSats) + drawGPS(); + if (ctr++%10 == 0) { + g.reset(); + Bangle.drawWidgets(); + } +} + +function restart(e) { + if (bangle2 && (e.time - e.lastTime > 0.5)) { + finish(); + } else { + g.reset(); + setColours(); + paused_time += (Date.now() - begin_pause); + pace = 0; + drawPause(); + oldDist = dist; + skip_ctr = 0; + force_write = true; + recording = true; + Bangle.buzz(); + if (!bangle2) + setWatch(pause, BTN2); + else + setWatch(pause, BTN1, { edge: 'falling' }); + } +} + +function pause(e) { + if (bangle2 && (e.time - e.lastTime > 0.5)) { + finish(); + } + g.reset(); + setColours(); + begin_pause = Date.now(); + elapsed_ms = begin_pause - (start_time + paused_time); + finish_ms = elapsed_ms; + drawDist(); + recording = false; + if (!show_lap) + drawTime(); + drawStart(); + if (!isNaN(gps.time) && !isNaN(gps.lat) && !isNaN(gps.lon) && !isNaN(gps.alt) && cfg.record && (last_time != gps.time)) + fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); + Bangle.buzz(); + dists = []; + if (!bangle2) + setWatch(restart, BTN2); + else + setWatch(restart, BTN1, { edge: 'falling' }); +} + +function start() { + g.reset(); + setColours(); + if (cfg.eco){ + Bangle.setLCDPower(true); + Bangle.setLCDTimeout(10); + } + if (cfg.record) + fp = require("Storage").open(genFilename(),"w"); + start_time = Date.now(); + drawPause(); + Bangle.buzz(); + started = true; + recording = true; + if (!bangle2) + setWatch(pause, BTN2); + else + setWatch(pause, BTN1, { edge: 'falling' }); + if (cfg.show_steps) + Bangle.on('step',countStep); + else + Bangle.on('step',doCadence); + clearInterval(intID); + intID = setInterval(mainLoop,200); + if (cfg.lap_idx > 0) + next_lap = parseFloat(laps[cfg.lap_idx]); +} + +function endScreen() { + fg = 1-fg; + setScreenMode(); + if (!bangle2) + drawInvert(); + drawExit(); + g.setFont("6x8",bangle2?1:2); + var dStr = dist.toString(); + if (dStr.indexOf('.') == -1) + dStr += '.00'; + dStr = dStr.slice(0, (dStr.indexOf('.'))+3); + if (bangle2) + g.drawString('Distance: '+dStr+(cfg.metric?'K':'M'),38,43); + else { + //g.drawString('Distance: '+dist.toFixed(2),19,53); + g.drawString('Distance: '+dStr,19,53); + g.setFont("6x8",1); + //g.drawString(cfg.metric?'K':'M',139+12*(dist.toFixed(2).length),60); + g.drawString(cfg.metric?'K':'M',139+12*(dStr.length),60); + g.setFont("6x8",2); + } + g.drawString('Time: '+parseInt(finish_ms/3600000)%10+':'+('0'+parseInt(finish_ms/60000)%60).substr(-2)+':'+('0'+parseInt(finish_ms/1000)%60).substr(-2),bangle2?62:67,bangle2?63:83); + var avgPace = dist?((finish_ms/dist)/1000):0; + var paceStr = 'Avg Pace: '+parseInt(avgPace/60)+':'+('0'+parseInt(avgPace%60)).substr(-2); + if (bangle2) + g.drawString(paceStr+(cfg.metric?'/K':'/M'),38,83); + else { + g.drawString(paceStr,19,113); + g.setFont("6x8",1); + g.drawString(cfg.metric?'/K':'/M',19+12*(paceStr.length),120); + g.setFont("6x8",2); + } + g.drawString("Steps: "+steps,bangle2?56:55,bangle2?103:143); + var avgCadence = steps?(60*steps/(finish_ms/1000)):0; + g.drawString("Cadence: "+parseInt(avgCadence),bangle2?44:31,bangle2?123:173); + g.reset(); + Bangle.drawWidgets(); +} + +function finish() { + if (recording) { + finish_ms = elapsed_ms; + if (!isNaN(gps.time) && !isNaN(gps.lat) && !isNaN(gps.lon) && !isNaN(gps.alt) && cfg.record && (last_time != gps.time)) + fp.write([gps.time.getTime(),gps.lat.toFixed(5),gps.lon.toFixed(5),gps.alt].join(",")+"\n"); + } + recording = false; + Bangle.setGPSPower(0); + Bangle.on('step',function(){}); + Bangle.on('GPS',function(){}); + clearInterval(lapID); + clearInterval(intID); + if (!bangle2) { + clearWatch(finID); + clearWatch(invID); + } + if (!bangle2) { + setWatch(function() {if (lcd_on) endScreen();}, BTN1, {repeat:true}); + setWatch(function() {load();},BTN3); + } else + setWatch(function() {load();},BTN1); + fg = 1-fg; + endScreen(); +} + +function startScreen() { + clearInterval(intID); + if (!bangle2) { + clearWatch(invID); + } + clearWatch(finID); + setScreenMode(); + doLayout(); + drawGPSBox(); + Bangle.buzz(); + if (!bangle2) { + invID = setWatch(invertRunning, BTN1, {repeat:true}); + startID = setWatch(start, BTN2); + finID = setWatch(finish, BTN3); + } else + startID = setWatch(start, BTN1, {edge: 'falling'}); + fixed = false; + intID = setInterval(mainLoop,1000); +} + +function invertRunning() { + // not applicable to bangle2 + if (!lcd_on) + return; + fg = 1-fg; + setScreenMode(); + if (started) + if (recording) { + current_time = Date.now(); + elapsed_ms = current_time - (start_time + paused_time); + } else + elapsed_ms = begin_pause - (start_time + paused_time); + else + elapsed_ms = 0; + drawInvert(); + drawSatIcon(); + drawGPSBox(); + drawGPS(); + drawDist(); + if (show_lap) + showLap(); + else { + drawTime(); + if (cfg.pacer == 0) + drawPace(); + else { + drawPacer(); + drawSmallPace(); + } + } + g.setFont("6x8",3); + if (cfg.show_steps) { + drawStepsIcon(); + g.drawString(steps.toString(),20,190,true); + } else { + drawCadenceIcon(); + g.drawString(cadence.toString()+" ",20,190,true); + } + if (recording) + drawPause(); + else if (started || gps.fix) + drawStart(); + drawStop(); + g.reset(); + Bangle.drawWidgets(); +} + +function drawDots() { + if (ctr % 4 == 0) + g.drawString(" ",bangle2?116:176,bangle2?90:108,true); + else if (ctr % 4 == 1) + g.drawString(".",bangle2?116:176,bangle2?90:108); + else if (ctr % 4 == 2) + g.drawString("..",bangle2?116:176,bangle2?90:108); + else + g.drawString("...",bangle2?116:176,bangle2?90:108); +} + +function invertWaiting() { + /* not applicable to bangle2 */ + fg = 1-fg; + setScreenMode(); + drawInvert(); + drawExit(); + g.setFont("6x8",2); + g.drawString("Locating",68,88); + g.drawString("Satellites",56,108); + ctr--; + drawDots(); + g.reset(); + Bangle.drawWidgets(); + ctr++; +} + +function awaitGPSLoop() { + g.reset(); + setColours(); + g.setFont("6x8",bangle2?1:2); + drawDots(); + if (gps.fix) + startScreen(); + if (ctr % 10 == 0) { + g.reset(); + Bangle.drawWidgets(); + } + ctr++; +} + +function awaitGPS() { + Bangle.setOptions({wakeOnTwist:false}); + Bangle.setGPSPower(1); + Bangle.on('GPS', saveGPS); + Bangle.setLCDPower(true); + Bangle.setLCDTimeout(0); + g.reset(); + setColours(); + if (!bangle2) { + drawInvert(); + invID = setWatch(invertWaiting, BTN1, {repeat:true}); + } + drawExit(); + g.setFont("6x8",bangle2?1:2); + // g.drawString("Locating",bangle2?36:68,bangle2?72:88); + // g.drawString("Satellites",bangle2?24:56,bangle2?92:108); + g.drawString("Locating",bangle2?62:68,bangle2?78:88); + g.drawString("Satellites",56,bangle2?90:108); + intID = setInterval(awaitGPSLoop,1000); + if (bangle2) + finID = setWatch(function(){load();},BTN1); + else + finID = setWatch(function(){load();},BTN3); +} + +function main() { + require("Storage").write("pacer.json", cfg); + E.showMenu(); + if (cfg.eco) + Bangle.on('lcdPower', function(on) {setTimeout(function(){lcd_on = on;}, 500);}); + fg = cfg.dark?1:0; + R = cfg.metric?6371:3959; + ppace = 295 + 5 * cfg.pacer; + setScreenMode(); + awaitGPS(); +} + +defaults(); + +var main_menu = { + "" : { "title" : "Pacer"}, + "Start": function() { main(); }, + "Recording" : { + value : cfg.record, + format : v => v?"On":"Off", + onchange : v => { cfg.record = v; }, + }, + "Units" : { + value : cfg.metric, + format : v => v?"Metric":"Imperial", + onchange : v => { cfg.metric = v; }, + }, + "Lap" : { + value : cfg.lap_idx, + format : v => laps[v], + min : 0, max : 6, + onchange : v => { cfg.lap_idx = v; } + }, + "Dark mode" : { + value : cfg.dark, + format : v => v?"On":"Off", + onchange : v => { cfg.dark = v; }, + }, + "Eco battery" : { + value : cfg.eco, + format : v => v?"On":"Off", + onchange : v => { cfg.eco = v; }, + }, + "Eco storage" : { + value : cfg.storage, + format : v => v?"On":"Off", + onchange : v => { cfg.storage = v; }, + }, + "Steps" : { + value : cfg.show_steps, + format : v => v?"Count":"Cadence", + onchange : v => { cfg.show_steps = v; }, + }, + "Pacer" : { + value : cfg.pacer, + format : v => v==0?"Off":pace_str(v), + min : 0, max : 121, + onchange : v => { cfg.pacer = v; } + }, +}; + +if (!bangle2) { + Bangle.setLCDMode(); +} +Bangle.setLCDBrightness(1); +setScreenMode(); +E.showMenu(main_menu); diff --git a/apps/pacer/app.png b/apps/pacer/app.png new file mode 100644 index 0000000000..9214805687 Binary files /dev/null and b/apps/pacer/app.png differ diff --git a/apps/pacer/interface.html b/apps/pacer/interface.html new file mode 100644 index 0000000000..71e8842d44 --- /dev/null +++ b/apps/pacer/interface.html @@ -0,0 +1,198 @@ + +
+ + + + + + + + + + + + diff --git a/apps/pacer/metadata.json b/apps/pacer/metadata.json new file mode 100644 index 0000000000..87d5fb1cec --- /dev/null +++ b/apps/pacer/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "pacer", + "name": "Pacer", + "version": "0.01", + "description": "Run with a virtual partner", + "icon": "app.png", + "tags": "run,running,fitness,outdoors,gps", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "screenshots": [{"url":"screenshot.png"}], + "storage": [ + {"name":"pacer.app.js","url":"app.js"}, + {"name":"pacer.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"pacer.json"}, + {"wildcard":".pacer*.csv","storageFile":true} + ] +} diff --git a/apps/pacer/screenshot.png b/apps/pacer/screenshot.png new file mode 100644 index 0000000000..d97f04d7d9 Binary files /dev/null and b/apps/pacer/screenshot.png differ diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json index 532aa8ccc8..5d1f925fe2 100644 --- a/apps/pastel/metadata.json +++ b/apps/pastel/metadata.json @@ -8,7 +8,7 @@ "dependencies": {"mylocation":"app","weather":"app"}, "screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}], "type": "clock", - "tags": "clock, weather, tool", + "tags": "clock,weather,tool", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/poweroff/metadata.json b/apps/poweroff/metadata.json index 218e4b441c..b9a7d60ea0 100644 --- a/apps/poweroff/metadata.json +++ b/apps/poweroff/metadata.json @@ -4,7 +4,7 @@ "version": "0.05", "description": "Simple app to power off your Bangle.js", "icon": "app.png", -"tags": "tool, poweroff, shutdown", +"tags": "tool,poweroff,shutdown", "supports" : ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", "allow_emulator": true, diff --git a/apps/pushups/ChangeLog b/apps/pushups/ChangeLog new file mode 100644 index 0000000000..6d4fa64411 --- /dev/null +++ b/apps/pushups/ChangeLog @@ -0,0 +1 @@ +0.1: Initial code diff --git a/apps/pushups/README.md b/apps/pushups/README.md new file mode 100644 index 0000000000..df2fe5358f --- /dev/null +++ b/apps/pushups/README.md @@ -0,0 +1,34 @@ +# Pushups + +Pushups is an exercising app with a twist : the accelerometer. + + + +I initially just wanted a pushups counter but i kind of got out of hand. + +The accelerometer will work on the following exercises : + +- pushups +- situps +- squats +- jumping jacks + +For each of them it will try to detect two positions (for example up and down for pushups) +and buzz on each change. You can set up a target counter for each exercise. + +Precision is not 100% but it's good for me and kind of increases my motivation. + +Other activities are time based like + +- plank +- rest + + +Define your training routine, set a duration and you're ready to go. + + +## Creator + +Feel free to give me feedback : is it useful for you ? what other features would you like ? + +frederic.wagner@imag.fr diff --git a/apps/pushups/app-icon.js b/apps/pushups/app-icon.js new file mode 100644 index 0000000000..3f98295ada --- /dev/null +++ b/apps/pushups/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/AH4A/ACcEogXVolEoAuVAAIXYhvdC6vdAAPQL6QuBAAaPRhtNDAgyQDQIXE7qYPC48iMaFM5gWC6kikAXPpvMDAXSC6BIBhoYB5oXBJB4XBOQPc5lCC4IYPC4StBCwUikgvQAAMCC4TlCeZgHFC4YYKFwoADkgXCDBIuHEQgACOpZTKC6oNGiIASC7YA/AH4A/AH4AdA")) diff --git a/apps/pushups/app.js b/apps/pushups/app.js new file mode 100644 index 0000000000..102a0a1416 --- /dev/null +++ b/apps/pushups/app.js @@ -0,0 +1,490 @@ +let h = require("heatshrink"); + +//TODO: should we force not moving for planks and rests + +const ACTIVITIES = ["pushups", "situps", "squats", "plank", "jacks", "rest"]; +const IMAGES = [ + h.decompress( + atob( + "mEwwhC/AH4A/AH4A/ACcEogXVolEoAuVAAIXYhvdC6vdAAPQL6QuBAAaPRhtNDAgyQDQIXE7qYPC48iMaFM5gWC6kikAXPpvMDAXSC6BIBhoYB5oXBJB4XBOQPc5lCC4IYPC4StBCwUikgvQAAMCC4TlCeZgHFC4YYKFwoADkgXCDBIuHEQgACOpZTKC6oNGiIASC7YA/AH4A/AH4AdA" + ) + ), + h.decompress( + atob( + "mEwwhC/AH4A/ADlEogWUggXBoAuVAAQXXDKQXGJaAXXO4RhVCYSSWGQTZWFy8wC6c0olDmYXTmdEmYXVAARITC4YwTgYXHkUgC6JICgUiDBxIFCwQwTIoIADC6MyC4hIRCwgwRFggwPC4JFESKRbGJB4uFYSMDLI5IPIA7aQkUwA4hIBE5AASC7IA/AH4A/ACwA=" + ) + ), + h.decompress( + atob( + "mEwwhC/AFUN7vQC6ndAAIuVAAIwTC/53fU7AAugUjmczmRjCPp4VBAAM9C4h+MgYXDloXRCwczCwpHMCoU4u51RIwc3uAXVw4XSLoZGSL35e/AB0IxAADxvdAAIXX6AX8gBfWGAcikgWCC54ADhpGRAAguDC65GWL35eyF4ZGTC7BJCCygA+" + ) + ), + h.decompress( + atob( + "mEwwhC/AH4A/AH4A/AHkEolAC6lEAAIuVAAIwTC6nd7oXTCoIAB6AXPgVNCwYwCO5siCopIEFxUCkUtC44AMkQX/CowACUogXLQAIXHbAgAHV4QXFTwa7IbwdEoQWCEg4sIC4kgHhAtJC4RULAAJQBC4qZKE4oXENwYXPP5KGLFJgA/AH4A/AH4AUA==" + ) + ), + h.decompress( + atob( + "mEwwhC/AFdEogWUggXBoAuVC7BIUC7RHPE4h3GAwKJPIwiVLBYQMHMgZNJBoY+EBBAYKoUikUkSaAXJSRxjBCwIXCeKMEC4UiCyIABC4bvTC/4AGhvdAAQXX6AXRCwYwSFwgwSCwoXQFwfQAgYXSGggwOFIowQCAQoEGB4nHD44XJBwxINEwRQOBo4MIJBouDmYACBQwAKgYWDAAMwa5wXCAoYXRADBIEIyQAGDJoWCCAgHHCxANIDBkDBawA/ADQA==" + ) + ), + h.decompress( + atob( + "mEwwhC/AH4AU1QAGC/4XXAAkKC6wWB0Au/F3KvPFw4XOLpAXOLpaBKRhgMBEZAKKHYYKKK5IjLC5alNC5BSOC5AuLe5guKABQuWd54XYgUiAAUgC7IAPC64ACC4IWUGIIu/F2a/va7QA/AH4AGA=" + ) + ) +]; + +// number of movements or duration required for each activity +const DEFAULTS = [7, 10, 10, 30, 15, 30]; + +// detector sensitivity for each activity +// (less is more reactive but more sensitive to noise) +const COUNTS = [6, 10, 6, 6, 6, 5]; + +function default_config() { + return {duration: 10*60, routine: default_routine()}; +} + +function default_routine() { + let routine = []; + DEFAULTS.forEach((d, i) => { + routine.push([i, d]); + }); + return routine; +} + +const DETECTORS = [ + (xyz) => { + "ram" + if (xyz.y > 0.15) { + return 1; + } else if (xyz.y < 0.1) { + return 0; + } else { + return null; + } + }, + (xyz) => { + "ram" + if (xyz.x > 0.075) { + return 1; + } else if (xyz.x < -0.075) { + return 0; + } else { + return null; + } + // return xyz.x > 0 ? 1 : 0; + }, + (xyz) => { + "ram" + if (xyz.z > -0.4) { + return 0; + } else if (xyz.z < -0.6) { + return 1; + } else { + return null; + } + }, + null, + (xyz) => { + "ram" + if (xyz.x < -0.8) { + return 0; + } + if (xyz.x > 0.3) { + return 1; + } + return null; + }, + null +]; + +class FitnessStatus { + constructor(config) { + this.routine = config.routine; + this.routine_step = 0; + this.current_status = 0; + this.buzzing = false; + + // to get rid of noise we'll need to count how many measures confirm where we think we are + this.counts_in_opposite_status = 0; + this.remaining = this.routine[this.routine_step][1]; + this.activity_start = getTime(); + this.starting_time = this.activity_start; + this.duration = config.duration; + this.completed = false; + } + + display() { + g.setBgColor(g.theme.bg); + g.clear(); + g.setColor(g.theme.fg); + if (this.completed) { + g.setFont("Vector:32") + .setFontAlign(0, 0) + .drawString("Good Job!", g.getWidth()/2, g.getHeight()/2); + return; + } + let activity = this.routine[this.routine_step][0]; + let countdown = this.remaining; + if (DETECTORS[activity] === null) { + countdown = this.remaining - Math.floor(getTime() - this.activity_start); + } + g.setFont("Vector:70") + .setFontAlign(0, 0) + .drawString("" + countdown, (g.getWidth() * 3) / 10, g.getHeight() / 2); + let activity_name = ACTIVITIES[activity]; + g.drawImage(IMAGES[activity], g.getWidth() / 2, (g.getHeight() * 1) / 5, { + scale: 2, + }); + let global_countdown = ""; + if (this.duration !== null) { + let elapsed = getTime() - this.starting_time; + let remaining = Math.max(0, this.duration - elapsed); + let seconds = Math.floor(remaining % 60); + let minutes = Math.floor(remaining / 60) % 60; + let hours = Math.floor(remaining / 3600); + if (hours > 0) { + global_countdown = " / " + hours + "h" + minutes +"m" + seconds + "s"; + } else if (minutes > 0) { + global_countdown = " / " + minutes +"m" + seconds + "s"; + } else { + global_countdown = " / " + seconds + "s"; + } + } + g.setFont("6x8:2") + .setFontAlign(0, 1) + .drawString(activity_name+global_countdown, g.getWidth() / 2, g.getHeight()); + Bangle.drawWidgets(); + g.flip(); + } + + first_activity() { + return this.routine_step == 0; + } + + last_activity() { + return this.routine_step == this.routine.length - 1; + } + + next_activity() { + this.routine_step += 1; + + this.completed = (this.duration===null)?(this.routine_step >= this.routine_length):(getTime() - this.starting_time > this.duration); + + if (this.completed) { + Bangle.buzz(1000).then(() => { + Bangle.setPollInterval(80); // default poll interval + Bangle.accelWr(0x18, 0b01101100); // off, +-4g + Bangle.accelWr(0x1B, 0x0); // default 12.5hz output + Bangle.accelWr(0x18, 0b11101100); // +-4g + load(); + }); + return; + } + this.routine_step = this.routine_step % this.routine.length; + this.remaining = this.routine[this.routine_step][1]; + // this.display(); + this.activity_start = getTime(); + this.current_status = 0; + this.counts_in_opposite_status = 0; + this.buzzing = false; + } + + previous_activity() { + this.routine_step -= 1; + this.remaining = this.routine[this.routine_step][1]; + // this.display(); + this.activity_start = getTime(); + this.current_status = 0; + this.counts_in_opposite_status = 0; + } + + detect(xyz) { + if (this.buzzing) { + return; + } + let activity = this.routine[this.routine_step][0]; + let detector = DETECTORS[activity]; + let status = this; + if (detector === null) { + // it's time based + let activity_duration = getTime() - this.activity_start; + if (activity_duration > this.remaining) { + this.buzzing = true; + Bangle.buzz(500).then(() => { + status.next_activity(); + }); + } + return; + } + // it's movement based + let new_status = DETECTORS[activity](xyz); + if (new_status === null) { + return; + } + if (new_status != this.current_status) { + this.counts_in_opposite_status += 1; + + if (this.counts_in_opposite_status == COUNTS[activity]) { + this.current_status = 1 - this.current_status; + this.counts_in_opposite_status = 0; + if (this.current_status == 0) { + this.remaining -= 1; + // this.display(); + if (this.remaining == 0) { + this.buzzing = true; + Bangle.buzz(500).then(() => { + status.next_activity(); + }); + } + } + Bangle.buzz(100); + } + } else { + this.counts_in_opposite_status = 0; + } + } +} + + +function start_routine(config) { + let status = new FitnessStatus(config); + + Bangle.accelWr(0x18,0b01110100); // off, +-8g // NOTE: this code is taken from 'accelrec' app + Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter + Bangle.accelWr(0x18,0b11110100); // +-8g + Bangle.setPollInterval(10); + + status.remaining = status.routine[status.routine_step][1]; + Bangle.loadWidgets(); + + Bangle.on("swipe", function (directionLR, directionUD) { + if (directionUD == -1) { + status.remaining += 1; + } else if (directionUD == 1) { + status.remaining = Math.max(status.remaining - 1, 1); + } else if (directionLR == -1) { + if (!status.last_activity()) { + status.next_activity(); + } + } else if (directionLR == 1) { + if (!status.first_activity()) { + status.previous_activity(); + } + } + // status.display(); + }); + + Bangle.on("accel", function (xyz) { + status.detect(xyz); + }); + + setInterval(() => { + status.display(); + }, 350); + +} + + +function edit_menu(config) { + let routine = config.routine; + + E.showScroller({ + h : 60, + c : routine.length+2, + draw : function(idx, r) { + g.setColor(g.theme.fg).drawRect(r.x, r.y, r.w, r.h); + if (idx == routine.length + 1) { + g.setFont("Vector:28").setFontAlign(0, 0).drawString("Ok", r.x+r.w/2, r.y+r.h/2); + } else if (idx == routine.length) { + g.setFont("Vector:28").setFontAlign(0, 0).drawString("Add", r.x+r.w/2, r.y+r.h/2); + } else { + let activity = routine[idx][0]; + let count = routine[idx][1]; + let img = IMAGES[activity]; + g.drawImage(img, r.x + r.w / 5, r.y + 10); + g.setFont("6x8:2").setFontAlign(0, 0).drawString(""+count, r.x+r.w*4/5, r.y+r.h/2); + } + }, + select : function(idx) { + if (idx == routine.length + 1) { + E.showScroller(); + set_duration(config); + } else if (idx == routine.length) { + E.showScroller(); + add_activity(config); + } else { + E.showScroller(); + set_counter(config, idx); + } + } + }); +} + + +function add_activity(config) { + E.showScroller({ + h : 60, + c : IMAGES.length, + draw : function(idx, r) { + g.setColor(g.theme.fg).drawRect(r.x, r.y, r.w, r.h); + let img = IMAGES[idx]; + g.drawImage(img, r.x + r.w / 3, r.y + 10); + }, + select : function(idx) { + let new_index = config.routine.length; + config.routine.push([idx, 10]); + E.showScroller(); + set_counter(config, new_index); + } + }); +} + + +function set_counter(config, index) { + let w = g.getWidth(); + let h = g.getHeight(); + let counter = config.routine[index][1]; + function display() { + g.clear(); + g.setFont("6x8:2") + .setFontAlign(1, 0) + .drawString("+1", w, h/2); + g.setFontAlign(-1, 0) + .drawString("-1", 0, h/2); + g.setFontAlign(0, -1) + .drawString("+5", w/2, 0); + g.setFontAlign(0, 1) + .drawString("-5", w/2, h); + g.setFont("Vector:64") + .setFontAlign(0, 0) + .drawString(""+counter, w/2, h/2); + } + display(); + Bangle.on("swipe", function (directionLR, directionUD) { + if (directionUD == -1) { + counter += 5; + } else if (directionUD == 1) { + counter -= 5; + } else if (directionLR == -1) { + counter -= 1; + } else if (directionLR == 1) { + counter += 1; + } + if (counter < 0) { + counter = 0; + } + display(); + }); + Bangle.on("touch", function(button, xy) { + if (counter == 0) { + config.routine.splice(index, 1); + } else { + config.routine[index][1] = counter; + } + Bangle.removeAllListeners("touch"); + Bangle.removeAllListeners("swipe"); + edit_menu(config); + }); + +} + + +//TODO: factorize code with set_counter +function set_duration(config) { + let w = g.getWidth(); + let h = g.getHeight(); + let duration = config.duration; + let minutes = Math.floor(duration / 60); + function display() { + g.setBgColor(g.theme.bg); + g.clear(); + g.setColor(g.theme.fg); + g.setFont("6x8:2") + .setFontAlign(1, 0) + .drawString("+1", w, h/2); + g.setFontAlign(-1, 0) + .drawString("-1", 0, h/2); + g.setFontAlign(0, -1) + .drawString("+5", w/2, 0); + g.setFontAlign(0, 1) + .drawString("-5", w/2, h); + g.drawString("minutes", w/2, h-40); + g.setFont("Vector:64") + .setFontAlign(0, 0) + .drawString(""+minutes, w/2, h/2); + } + display(); + Bangle.on("swipe", function (directionLR, directionUD) { + if (directionUD == -1) { + minutes += 5; + } else if (directionUD == 1) { + minutes -= 5; + } else if (directionLR == -1) { + minutes -= 1; + } else if (directionLR == 1) { + minutes += 1; + } + if (minutes < 1) { + minutes = 1; + } + display(); + }); + Bangle.on("touch", function(button, xy) { + Bangle.removeAllListeners("touch"); + Bangle.removeAllListeners("swipe"); + config.duration = minutes * 60; + //TODO: don't write if no change + require("Storage").writeJSON("pushups.cfg", config); + start_routine(config); + }); + +} + +function main_menu(config) { + let w = g.getWidth(); + let h = g.getHeight(); + g.setBgColor(g.theme.bg); + g.clear(); + g.setColor(g.theme.fg); + g.drawRect(10, 10, w-10, h/2-10); + g.drawRect(10, h/2+10, w-10, h-10); + g.setFont("Vector:32") + .setFontAlign(0, 0) + .drawString("Start", w/2, h/4); + g.drawString("Edit", w/2, 3*h/4); + Bangle.setLocked(false); + Bangle.on("touch", function(button, xy) { + if (xy.y > h/2+10) { + g.setColor(g.theme.fg); + g.fillRect(10, h/2+10, w-10, h-10); + g.setColor(g.theme.bg) + .setFont("Vector:32") + .setFontAlign(0, 0) + .drawString("Edit", w/2, 3*h/4); + Bangle.removeAllListeners("touch"); + edit_menu(config); + } else if (xy.y < h/2-10) { + g.setColor(g.theme.fg); + g.fillRect(10, 10, w-10, h/2-10); + g.setColor(g.theme.bg) + .setFont("Vector:32") + .setFontAlign(0, 0) + .drawString("Start", w/2, h/4); + Bangle.removeAllListeners("touch"); + set_duration(config); + } + }) +} + + +let config = require("Storage").readJSON("pushups.cfg", true); + +if (config === undefined) { + config = default_config(); +} + +main_menu(config); diff --git a/apps/pushups/metadata.json b/apps/pushups/metadata.json new file mode 100644 index 0000000000..dc8dd69897 --- /dev/null +++ b/apps/pushups/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "pushups", + "name": "Pushups", + "shortName": "Pushups", + "version": "0.1", + "description": "Pushups countdown using the accelerometer.", + "allow_emulator":false, + "icon": "pushups.png", + "type": "app", + "tags": "health", + "supports": ["BANGLEJS2"], + "screenshots": [{"url":"shot_pushups.png"}, {"url":"shot_squats.png"}, {"url":"shot_menu.png"}], + "readme": "README.md", + "storage": [ + {"name":"pushups.app.js","url":"app.js"}, + {"name":"pushups.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/pushups/pushups.png b/apps/pushups/pushups.png new file mode 100644 index 0000000000..66c5902b6d Binary files /dev/null and b/apps/pushups/pushups.png differ diff --git a/apps/pushups/shot_menu.png b/apps/pushups/shot_menu.png new file mode 100644 index 0000000000..b7f44b6e20 Binary files /dev/null and b/apps/pushups/shot_menu.png differ diff --git a/apps/pushups/shot_pushups.png b/apps/pushups/shot_pushups.png new file mode 100644 index 0000000000..3f22ee8d1a Binary files /dev/null and b/apps/pushups/shot_pushups.png differ diff --git a/apps/pushups/shot_squats.png b/apps/pushups/shot_squats.png new file mode 100644 index 0000000000..de3ac7bdd1 Binary files /dev/null and b/apps/pushups/shot_squats.png differ diff --git a/apps/quicklaunch/metadata.json b/apps/quicklaunch/metadata.json index 15c40dfdca..95ae0b014a 100644 --- a/apps/quicklaunch/metadata.json +++ b/apps/quicklaunch/metadata.json @@ -5,7 +5,7 @@ "version": "0.16", "description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice. Configurations can be accessed through Settings->Apps.", "type": "bootloader", - "tags": "tools, system", + "tags": "tools,system", "readme": "README.md", "supports": ["BANGLEJS2"], "storage": [ diff --git a/apps/recorder/README.md b/apps/recorder/README.md index 6da739b1cd..d04e0342d6 100644 --- a/apps/recorder/README.md +++ b/apps/recorder/README.md @@ -52,6 +52,23 @@ As long as widgets are loaded, you can: * Call `WIDGETS["recorder"].setRecording(true, {force:"new"/"append"/"overwrite")` to start recording (it returns a promise, and will not show a menu) * Call `WIDGETS["recorder"].setRecording(false)` to stop recording +### Recording new items + +You can add new data items to record by creating a JS file on the Bangle named ending in `.recorder.js` that adds a new item +to the supplied `recorders` array. For example `foobar.recorder.js` could contain: + +``` +(function(recorders) { + recorders.foobar = { + name : "Foobar", // Name to appear in UIs + fields : ["foobar"], // Column headings to appear as header in recorded CSV data + getValues : () => [123], // Columns of data (length should match 'fields') + start : () => {}, // Called when recording starts - turn on any hardware/intervals you need + stop : () => {}, // Called when recording stops - turn off any hardware/intervals + draw (x,y) => {} // draw 12x12px status image at x,y on g + } +}) +``` ## Tips diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 74cffb068f..740df922f8 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -145,15 +145,17 @@ } } - /* eg. foobar.recorder.js + /* You can add new data items to record by creating a JS file on the Bangle named ending in `.recorder.js` that adds a new item +to the supplied `recorders` array. For example `foobar.recorder.js` could contain: + (function(recorders) { recorders.foobar = { - name : "Foobar", - fields : ["foobar"], - getValues : () => [123], - start : () => {}, - stop : () => {}, - draw (x,y) => {} // draw 12x12px status image + name : "Foobar", // Name to appear in UIs + fields : ["foobar"], // Column headings to appear as header in recorded CSV data + getValues : () => [123], // Columns of data (length should match 'fields') + start : () => {}, // Called when recording starts - turn on any hardware/intervals you need + stop : () => {}, // Called when recording stops - turn off any hardware/intervals + draw (x,y) => {} // draw 12x12px status image at x,y on g } }) */ diff --git a/apps/rescalc/metadata.json b/apps/rescalc/metadata.json index cd2b7ea310..f7f7c5e29b 100644 --- a/apps/rescalc/metadata.json +++ b/apps/rescalc/metadata.json @@ -10,7 +10,7 @@ {"url": "screenshot-2.png"} ], "description": "An intuitive, easy-to-use app that aids in the interpretation of resistor color codes and calculation of resistance values.", - "tags": "app, tool, electricity, ohms, converter", + "tags": "app,tool,electricity,ohms,converter", "supports": ["BANGLEJS2"], "readme": "README.md", "allow_emulator": true, diff --git a/apps/sched/README.md b/apps/sched/README.md index 1216a1a11f..0fa780405e 100644 --- a/apps/sched/README.md +++ b/apps/sched/README.md @@ -49,7 +49,7 @@ Alarms are stored in an array in `sched.json`, and take the form: // e.g. repeat every 2 months: { interval: "month", num: 2 }. // Supported intervals: day, week, month, year vibrate : "...", // OPTIONAL pattern of '.', '-' and ' ' to use for when buzzing out this alarm (defaults to '..' if not set) - hidden : false, // OPTIONAL if false, the widget should not show an icon for this alarm + hidden : false, // OPTIONAL if true, the widget should not show an icon for this alarm as : false, // auto snooze timer : 5*60*1000, // OPTIONAL - if set, this is a timer and it's the time in ms del : false, // OPTIONAL - if true, delete the timer after expiration diff --git a/apps/scribble/metadata.json b/apps/scribble/metadata.json index 2c31be931a..19c6c707c8 100644 --- a/apps/scribble/metadata.json +++ b/apps/scribble/metadata.json @@ -6,7 +6,7 @@ "description": "A keyboard on your wrist! Swipe right for space, left for delete.", "icon": "app.png", "allow_emulator": true, - "tags": "tools, keyboard, text, scribble", + "tags": "tools,keyboard,text,scribble", "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/sevenmin/metadata.json b/apps/sevenmin/metadata.json index 5103a5f970..8a1867e4a0 100644 --- a/apps/sevenmin/metadata.json +++ b/apps/sevenmin/metadata.json @@ -5,7 +5,7 @@ "description": "A basic implementation of the famous consice workout", "icon": "icon.png", "type":"app", - "tags": "Training, Workout", + "tags": "training,workout", "supports": ["BANGLEJS2"], "readme": "README.md", "allow_emulator":true, diff --git a/apps/spaceclock/metadata.json b/apps/spaceclock/metadata.json index 1e8bc266fd..cff458d085 100644 --- a/apps/spaceclock/metadata.json +++ b/apps/spaceclock/metadata.json @@ -5,8 +5,8 @@ "description": "Watch face in the style of Casio Prototype Space Resist", "icon": "app-icon.png", "type": "clock", - "tags": "clock, casio, retro", - "supports" : ["BANGLEJS2"], + "tags": "clock,casio,retro", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"spaceclock.app.js","url":"app.js"}, diff --git a/apps/spacer/ChangeLog b/apps/spacer/ChangeLog index 263d4078d2..88e8a64fc0 100644 --- a/apps/spacer/ChangeLog +++ b/apps/spacer/ChangeLog @@ -1 +1,2 @@ 0.01: attempt to import +0.10: major updates, display satellites, display per-system satellite info diff --git a/apps/spacer/README.md b/apps/spacer/README.md index 5a8725a966..b3fc437ba7 100644 --- a/apps/spacer/README.md +++ b/apps/spacer/README.md @@ -4,5 +4,17 @@ Compare GPS with Baido and Glonass This turns GNSS receiver into mode with all three systems enabled, and displays debug info from all of them. Click into top left corner to -switch navigation systems. +switch navigation systems. Clicks in bottom half of screen switch info +pages. + +GNSS acquisition has few phases, and this software is assuming you are +not doing a cold start. GNSS fix needs 4 or 5 satellites with +reasonable signal strength, and then holding the same place for 40 or +so seconds. + +Uxx -- satellites are known but not being received + +S1..S4 -- not enough satellites being decoded (5 needed) + +XXdB -- this is strength of 5th strongest satellite diff --git a/apps/spacer/app-icon.js b/apps/spacer/app-icon.js index 903d14af51..53dbfa31ee 100644 --- a/apps/spacer/app-icon.js +++ b/apps/spacer/app-icon.js @@ -1,2 +1,2 @@ -require("heatshrink").decompress(atob("mEwgIQNgQFEj/gAof+jgECgeAAIIFBgwCBuACBhgCEjAOEAoQ6CmAhCDwItDoEB4AFCsEBFgUEkEDG4XEJYcL8gFCgUP+gxCAoP8DIIFBhfsiEIAoMJAogCBAoYlBiBMBAoUwrA0B////ALECI0QAocgAolgApVADolAHYnAAomAAoqdBAoKVBMoRvCOIQDCRIIFBYwKVBAoKqC4AFBVQVggTRDn0CYgQcBN4LpDV4T7IAooAJA=")) +require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AGMAF34v/F34EBAAIvrFwQxnEoIsFGEyNHF9ZgNGrheMF4guJHDAvLEhCdbLyLLdL5ImFfRo6SLpjGOCgw5TdZRhKHwhEPFxjOJCwwOGF6zOJBxpjFAB4TKR6aEJDpgwKd5rzUYBrkVMxwtVF7CYHeBxGRF5LwMR9YvdPiJfjBhYv/F/4v/F/4v/F/4v/F/4v/F/4vfAH4Ad")) diff --git a/apps/spacer/app.png b/apps/spacer/app.png index ce7ac239fd..6743d6e592 100644 Binary files a/apps/spacer/app.png and b/apps/spacer/app.png differ diff --git a/apps/spacer/metadata.json b/apps/spacer/metadata.json index 55598f9535..e114d8dd12 100644 --- a/apps/spacer/metadata.json +++ b/apps/spacer/metadata.json @@ -1,6 +1,6 @@ { "id": "spacer", "name": "Space Race", - "version": "0.01", + "version": "0.10", "description": "Compare GPS with Baido and Glonass", "icon": "app.png", "readme": "README.md", diff --git a/apps/spacer/spacer.app.js b/apps/spacer/spacer.app.js index 13812b2a59..99453e79a8 100644 --- a/apps/spacer/spacer.app.js +++ b/apps/spacer/spacer.app.js @@ -1,20 +1,6 @@ /* Space race */ -/* - -Performance Assessment of GNSS Signals in -terms of Time to First Fix for -Cold, Warm and Hot Start -Matteo Paonni, Marco Anghileri, Stefan Wallner, José-Ángel Ávila-Rodríguez, Bernd Eissfeller -Institute of Geodesy and Navigation, University FAF Munich, Germany - -=> 22 to 26 dB -- long time / no fix / ... -=> 26db + -- basically strength no longer matters - -apps/assistedgps/custom.html - -https://github.com/espruino/EspruinoDocs/blob/master/info/Bangle.js2%20Technical.md#gps - +/* gsa mi rika 2d/3d fix, a taky pdop/vdop/hdop CFG-NAVX z CASIC_en -- umoznuje nastavit chodec / auto / letadlo @@ -68,12 +54,12 @@ let ui = { touchHandler: function(d) { let x = Math.floor(d.x); let y = Math.floor(d.y); - + if (d.b != 1 || this.last_b != 0) { this.last_b = d.b; return; } - + print("touch", x, y, this.h, this.w); if ((x