diff --git a/apps/golf-gps/ChangeLog b/apps/golf-gps/ChangeLog new file mode 100644 index 000000000..03c5da842 --- /dev/null +++ b/apps/golf-gps/ChangeLog @@ -0,0 +1,2 @@ +0.01: First release + diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md new file mode 100644 index 000000000..b67a2255d --- /dev/null +++ b/apps/golf-gps/README.md @@ -0,0 +1,61 @@ +# Golf GPS +I have made a few watches for golfing. See this [LINK](https://jeonlab.wordpress.com/category/golf-gps-watch/) if you are interested. +Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfing. For my previous watches I have used TFT LCD or OLED displays and they all draw a lot of current and display information only when I press a button to wake up from the black screen. One of the best feature of the Bangle.js 2, I think, is the memory LCD which consumes very small power and it is always on! + +## Features +- Play or view previously played scores +- Save your favourite course data (see below instruction) +- In play mode + - Hole number + - Par as background color of the hole number (red: 3, green: 4, blue: 5) + - Distance to the center of the green (coordinates you saved) + - Distance from the last shot (where you swiped up to add a shot) + - Number of shots on current hole + - Total number of shots + - Clock + +![](playScreen.png) +- How to change holes and add/subtract shots + - Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake) + - Swipe up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots. +- After the game + - Press the button to either finish the game or go back to the play screen (if you pressed the button by accident). + - If you choose to finish the game, it will show the summary of the score with 3x6 matix, shots - par. For example, -1 is for birdie and 0 is for par, and +2 is for double bogey. It also shows the total shots and total par as well. + +## Course data +Before you run Golf GPS, you need to save your favourite golf course data into the storage area. +Download `storage-write.js` and open it from Espruino Web IDE (https://www.espruino.com/ide/) and enter your course data and run in RAM mode. +With this script, you can create, overwrite or append golf course GPS data. +``` +f = require("Storage").open("course-data","a"); // "w" to create or overwrite, "a" to append +``` +There must be other ways to get the GPS coordinates for your course, but I use Google Maps. In satellite mode, click on the middle of each green will show you the coordinate, latitude and longitude. copy and paste it into the script for the holes 1 through 18 and add par per each hole. You will need 6 decimal places for the coordinate. Here is a format of the course data to put in the script. + +``` +"course name\n"+ +"00.000000,00.000000,0\n"+ +. +. +"00.000000,00.000000,0\n"+ +"next course name\n"+ +. +. +``` +You can put any number of course data. Once the data file is created in the storage area, you can add more data to the same file using append ("a" ) mode with the same script. Just replace the data and change the mode to "a" and run in RAM mode. + +One tip to wrap each line with `"----\n"+` is using the built-in edit feature of the Web IDE. In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all lines including the course name, and hit HOME and type `"` and hit END and type `\n"+`. + +## Screenshots +![](startUp.png) +![](fixGPS.png) + +![](par3.png) +![](par4.png) +![](par5.png) + +![](finishGame.png) +![](scoreView.png) + +## Creator + +Written by [JeonLab](https://jeonlab.wordpress.com) diff --git a/apps/golf-gps/finishGame.png b/apps/golf-gps/finishGame.png new file mode 100644 index 000000000..5f05f4b74 Binary files /dev/null and b/apps/golf-gps/finishGame.png differ diff --git a/apps/golf-gps/fixGPS.png b/apps/golf-gps/fixGPS.png new file mode 100644 index 000000000..201a6c3b5 Binary files /dev/null and b/apps/golf-gps/fixGPS.png differ diff --git a/apps/golf-gps/golf-gps-icon.js b/apps/golf-gps/golf-gps-icon.js new file mode 100644 index 000000000..cc0e3a552 --- /dev/null +++ b/apps/golf-gps/golf-gps-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AHeB1nOBhMsAAQve1gAB1WjvIuIhEIlYuc1YvC0ejlcsmVWBYNVFgIADMLgvC1XM4wKEqtVlkrp8rGAQvb1Wr1X+43NBYtUFoIwCYLvN5wsC5owFv1PAAIxBhFVY4MymQvdAgIxD415vNVp9OqgYFHAMyfKYoBFIYFCGwfG0YxBvwcKGYIvW44AB5uiy/B4PGfIowadYIBB5vH3e7y4AGF74wBF4XBFwedF8oxDLg4vQAC+dAAIvsL1yOvF/6+/d34v/F/4v+uYuHuYvlhErp9PuYACp8rF8wAGlksF9oABF1wvug8HR9guBF88sRISPpF+QwBF+AwqFwKQDAoQGDL8woCGFAvGAwQwmEwwwDYchWHNAdPq1PSERVGGAWXq0yuYvgAAYJFle7F4OXMEK2IF4ZfgYAIuBlYKGR4MsYEOXudzy6FGucyF8X+3YuBF4zxBNI4AllZdiAD4A==")) \ No newline at end of file diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js new file mode 100644 index 000000000..8c730442b --- /dev/null +++ b/apps/golf-gps/golf-gps.js @@ -0,0 +1,309 @@ +/* + Golf-GPS app v0.01 + written by JeonLab (https://jeonlab.wordpress.com) + 6/26/2024 +*/ + +var currentHole = 1, + totalShots = 0, + courseName = "", + latLast, + lonLast, + W = g.getWidth(), + H = g.getHeight(), + lat = Array(19).fill(0), + lon = Array(19).fill(0), + par = Array(19).fill(0), + score = Array(19).fill(0), + playON = false; + +var fileIndx; +var scoreFiles; + +require("Font7x11Numeric7Seg").add(Graphics); + +Graphics.prototype.setFontDroidSansMono52 = function() { + // Actual height 52 (53 - 2) + // 1 BPP + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('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'))), + 46, + atob("HCQnHCYmKCYmJyYmGw=="), + 70 | 65536 + ); +}; + +Graphics.prototype.setFontDroidSansMono35 = function() { + // Actual height 35 (37 - 3) + // 1 BPP + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AH/wAgcB/AFDgf8ArYjFF4oAehAFEnAFELIoFEj4FEv4FDgP/8AFCh//wAFCn/+Cwf/LAcH//AE4f/E5EDE5UfE4kfQAkfE4YFBMIkYRjo8BIQd//49COoIABJIJTBAAI+BNQIABDAIcBAAJQBh4IBg4FBj4aBcYRTDAoMeAoV4AqQdDAoopCF4kPHwQCCI4hTFL4rQBAALcD//8YwivEAEVgAoj6DAoxiCIAb1Eg7JDAoJLEh59BAAUfMoSJCArgAbZYMDNAhTCNAQFCgbRCAoMHAoRfBj5BCYIQFEv4pBjwFB/wFDgP8AoYoBAocH+AFDh/gAoIjBn/AAod/wAFC4b6BAoMP//+Aog/BAoMH/7ABNYX/YAIFBgAcBAoZ9EApIAQgIrBTYUBG4MHRIIFCSoSnCeoYFHNYM8HYQFFQYIFRvgFOFIKPBGoWBAonH95TD//v44FD56dCUIPHToShBw//NAX+A4JiCOwKpCABxIBAAf8AgcDAokHAokfAok/Aon/PwI6C/wFDg/4AocP+AFDn/AAod/wAFCDgKiCH4YFCDgIFDj56BAof/AAKbCAqwACIIYFZAC8BLgJsB8DvCI4PwAosAKYYDBAoYLBvAFGj0AAsY1DAo/8JoQFBv7CCAoX/MoIFBn4FEJ4PAewf/wAFCg7rBADR4CFAYjDHQIvDdIQ7BgIFCI4MDAoQeBg/8nkHAoJiBvEBOIMP4B3Dh+AWYiPEAoqnPUJUDAoabBUJoABUI6DDLAQAYF4IFZhAFEnAFEMoaqBAokfAol/AobHBO4ZlBNYJ3CcYQFB/5rCj0HQQceQQJHDv/8RoiTFf4QFBE4QFCHAIFDUgbjCAD1+QAZrBHoMD/0DNYReBw5PCAoPPPoR7BAov/wJ4Bj4eBU4UfgIFBvCJCO4IFDB4IFDAYIRJDoYjDFIWDUIIFBHYpHFLIYFDYAYiBNYYsCWokAnifan7GEHIQABGYJLBuBBCNYZTBNYYFFj+An4FDAIQFDh6JEApo3BAoodCgJJBFIUDAoWAn0PRIICBvl/d4YABfYYABR4JlBAAJxDMwR9CPAkHMoIA/ABUPMYKJBPwPAgJgCAq4jFAAg'))), + 46, + atob("FBkbFBsaHBobGxsbEw=="), + 48 | 65536 + ); +}; + +var courseData = require("Storage").open("course-data", "r"); + +var lastFix = { + lat: 0, + lon: 0, + alt: 0, // altitude in m + speed: 0, // km/h + course: 0, // heading in degrees + time: 0, + satellites: 0, + fix: 0, + hdop: 0, // x5 ~ meter accuracy +}; + +const zeroPad = (num, places) => String(num).padStart(places, '0'); + +function onGPS(fix) { + Object.assign(lastFix, fix); + if (lastFix.fix && playON) showPlayData(); +} + +function radians(degrees) { + return degrees * Math.PI / 180; +} + +function readOneCourseData() { + // Clear previous data + lat = []; + lon = []; + par = []; + let l = courseData.readLine(); + courseName = l; + // Read one course data & initialize score + for (let i = 1; i < 19; i++) { + l = courseData.readLine(); + if (l !== undefined) { + const parts = l.split(','); + lat[i] = parseFloat(parts[0]); + lon[i] = parseFloat(parts[1]); + par[i] = parseInt(parts[2]); + score[i] = 0; + } + } +} + +function distanceCalc(lat1, long1, lat2, long2) { + let delLat = Math.abs(lat1 - lat2) * 111194.9; // 111194.9 = (2*6371000*pi)/360, 6371000 ~ Earth's average radius + let delLong = Math.abs(long1 - long2) * 111194.9 * Math.cos(radians((lat1+lat2)/2)); + return Math.sqrt(delLat * delLat + delLong * delLong)/0.9144; // in yards +} + +function mainMenu() { + E.showPrompt("Play now or\n\nView scores?", { + title: "Golf GPS", + buttons: { + "PLAY": 1, + "VIEW": 2 + } + }).then(choice => { + if (choice === 1) fixGPS(); + else browseScore(); + }); +} + +function browseScore() { + scoreFiles = require("Storage").list(/^Scorecard-/); + if (scoreFiles.length === 0) { + E.showPrompt("No score file found.\n\nYou need to play at least a game.", { + title: `Error`, + buttons: { + "END": 1 + } + }).then(choice => { + if (choice === 1) load(); + }); + } + fileIndx = scoreFiles.length - 1; + browseFiles(); +} + +function browseFiles() { + const browsefile = require("Storage").open(scoreFiles[fileIndx].substring(0, 18), "r"); + const l = browsefile.read(80); + + E.showPrompt(l, { + buttons: { + "<<": 1, + ">>": 2, + "End": 3 + } + }).then(choice => { + if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length; + else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length; + else if (choice === 3) load(); + browseFiles(); + }); +} + +function fixGPS() { + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1, "golf-gps"); + E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n "); + + let fixInterval = setInterval(() => { + let date = new Date(); + g.clearRect(0, 150, W, H); + g.setFontAlign(1, 1).setFont('6x8:3').drawString(lastFix.hdop, W, H); + g.setFontAlign(-1, 1).setFont('6x8:3').drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), 2, H); + if (lastFix.fix && lastFix.hdop <= 5) { + clearInterval(fixInterval); + searchCourse(); + } + }, 1000); // Check every second +} + +function searchCourse() { + readOneCourseData(); + if (!courseName) { + courseData = require("Storage").open("course-data", "r"); + E.showPrompt("Scan again\nor quit?", { + title: "No course found", + buttons: { + "SCAN": 1, + "QUIT": 2 + } + }).then(choice => { + if (choice === 1) searchCourse(); + else load(); + }); + } else if (distanceCalc(lastFix.lat, lastFix.lon, lat[1], lon[1]) < 1000) { + Bangle.buzz(); + E.showPrompt(courseName + "\nIs this correct?", { + buttons: { + " YES ": 1, + " NO ": 2 + } + }).then(choice => { + if (choice == 1) { + E.showPrompt("Front or Back", { + title: courseName, + buttons: { + "FRONT": 1, + "BACK": 2 + } + }).then(choice => { + currentHole = (choice === 1) ? 1 : 10; + playON = true; + showPlayData(); + }); + } else searchCourse(); + }); + } else searchCourse(); +} + +function showPlayData() { + let date = new Date(); + let distanceToHole = distanceCalc(lastFix.lat, lastFix.lon, lat[currentHole], lon[currentHole]); + let distanceFromLast = distanceCalc(lastFix.lat, lastFix.lon, latLast, lonLast) || 0; + + g.reset().clearRect(Bangle.appRect); + + // distance to hole in yards + g.setColor(g.theme.fg).setFontAlign(1, -1).setFontDroidSansMono52(); + g.drawString(distanceToHole > 1000 ? "000" : distanceToHole.toFixed(0), W - 2, 1); + + // distance from last shot in yards + g.setFontDroidSansMono35().setColor('#00f').drawString(distanceFromLast.toFixed(0), W - 2, 70); + + // hole number and par color(red:3, green:4, blue:5) + g.setColor(par[currentHole] == 3 ? '#f00' : (par[currentHole] == 4 ? '#0f0' : '#00f')); + g.fillRect(3, 3, 47, 47); + g.setColor(par[currentHole] == 4 ? '#000' : '#fff').setFontAlign(0, -1).drawString(currentHole, 24, 5); + + // total shots and current shots + g.setColor(g.theme.fg).setFontAlign(-1, -1); + g.drawString(totalShots, 3, 70); // total shots + g.drawString(score[currentHole], 3, 128); // current shots + g.setFont("6x8:2").drawString('TOTAL', 3, 54); + g.drawString('THIS', 3, 112); + + // clock + g.setFontAlign(1, 1).setFont("7x11Numeric7Seg:4").drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 2, H - 4); + g.drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 1, H - 3); + + // battery level bar + g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4); + g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5); + g.setColor(E.getBattery() > 15 ? '#03f' : '#f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 60), H * 2 / 3 + 3); + + // hdop level indicator + if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5); + if (lastFix.hdop < 2) g.fillRect(100, H / 3, 136, H / 3 + 5); + if (lastFix.hdop < 1) g.fillRect(140, H / 3, W, H / 3 + 5); +} + +function finishGame() { + let date = new Date(); + let saveFileContents = ' '; + let parTotal = 0; + + Bangle.setGPSPower(0, "golf-gps"); + const saveFilename = `Scorecard-${date.getFullYear()}${zeroPad(date.getMonth() + 1, 2)}${zeroPad(date.getDate(), 2)}`; + const saveFile = require("Storage").open(saveFilename, "w"); + for (let i = 1; i < 19; i++) { + saveFileContents += String(score[i] - par[i]).padStart(2, ' '); + saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : '')); + parTotal += par[i]; + } + saveFile.write(`${date.getFullYear()}_${zeroPad(date.getMonth() + 1, 2)}_${zeroPad(date.getDate(), 2)}\n${courseName}${totalShots}/${parTotal}\n${saveFileContents}`); + E.showPrompt(saveFileContents, { + title: `${totalShots}/${parTotal}`, + buttons: { + "END": 1 + } + }).then(choice => { + if (choice === 1) load(); + }); +} + +setWatch(() => { + playON = false; + E.showPrompt("Finish game?", { + title: courseName, + buttons: { + " YES ": 1, + " NO ": 2 + } + }).then(choice => { + if (choice === 1) { + finishGame(); + } else { + playON = true; + showPlayData(); + } + }); +}, (process.env.HWVERSION === 2) ? BTN1 : BTN2, { + repeat: true, + edge: "falling" +}); + +Bangle.on('swipe', function(directionLR, directionUD) { + if (playON) { + currentHole = (currentHole - directionLR) % 18 || 18; + score[currentHole] = Math.max(0, score[currentHole] - directionUD); + totalShots = Math.max(0, totalShots - directionUD); + if (directionUD === -1) { + latLast = lastFix.lat; + lonLast = lastFix.lon; + } + } +}); + +Bangle.loadWidgets(); +require("widget_utils").hide(); + +// keep unlocked +Bangle.setLCDTimeout(0); +Bangle.setLocked(false); + +// keep backlight off +Bangle.setLCDBrightness(0); + +mainMenu(); diff --git a/apps/golf-gps/golf-gps.png b/apps/golf-gps/golf-gps.png new file mode 100644 index 000000000..d8faaa7f6 Binary files /dev/null and b/apps/golf-gps/golf-gps.png differ diff --git a/apps/golf-gps/metadata.json b/apps/golf-gps/metadata.json new file mode 100644 index 000000000..760d673e5 --- /dev/null +++ b/apps/golf-gps/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "golf-gps", + "name": "Golf GPS", + "version": "0.01", + "description": "Golf GPS for Banglejs 2, using manually saved golf course data (latitudes and longitudes of holes, see README for instructions), provides distance to center of green and distance from previous shot, keeps score", + "icon": "golf-gps.png", + "type": "app", + "tags": "gps,outdoors,tools", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"golf-gps.app.js","url":"golf-gps.js"}, + {"name":"golf-gps.img","url":"golf-gps-icon.js","evaluate":true} + ], + "readme":"README.md" +} diff --git a/apps/golf-gps/par3.png b/apps/golf-gps/par3.png new file mode 100644 index 000000000..11a640880 Binary files /dev/null and b/apps/golf-gps/par3.png differ diff --git a/apps/golf-gps/par4.png b/apps/golf-gps/par4.png new file mode 100644 index 000000000..e1fc5dbde Binary files /dev/null and b/apps/golf-gps/par4.png differ diff --git a/apps/golf-gps/par5.png b/apps/golf-gps/par5.png new file mode 100644 index 000000000..aa32b5e8b Binary files /dev/null and b/apps/golf-gps/par5.png differ diff --git a/apps/golf-gps/playScreen.png b/apps/golf-gps/playScreen.png new file mode 100644 index 000000000..0b157f745 Binary files /dev/null and b/apps/golf-gps/playScreen.png differ diff --git a/apps/golf-gps/scoreView.png b/apps/golf-gps/scoreView.png new file mode 100644 index 000000000..e0fb2c970 Binary files /dev/null and b/apps/golf-gps/scoreView.png differ diff --git a/apps/golf-gps/startUp.png b/apps/golf-gps/startUp.png new file mode 100644 index 000000000..18a4da846 Binary files /dev/null and b/apps/golf-gps/startUp.png differ diff --git a/apps/golf-gps/storage-write.js b/apps/golf-gps/storage-write.js new file mode 100644 index 000000000..69b4e319c --- /dev/null +++ b/apps/golf-gps/storage-write.js @@ -0,0 +1,56 @@ +/* + Before you run Golf GPS, you need to save your favourite golf course data into the storage area. + Download this script and open it from Espruino Web IDE (https://www.espruino.com/ide/) and enter your course data and run in RAM mode. + With this script, you can create, overwrite or append golf course GPS data. + + You can put any number of course data. + Once the data file is created in the storage area, you can add more data to the same file using append ("a" ) mode with the same script. + Just replace the data and change the mode to "a" and run in RAM mode. + + One tip to wrap each line with "----\n"+ is using the built-in edit feature of the Web IDE. + In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all lines including the course name, + and hit HOME and type " and hit END and type \n"+. +*/ + +const Storage = require("Storage"); +const f = Storage.open("course-data", "a"); // "w" to create or overwrite, "a" to append +f.write( +"course name\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"course name\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n" +); diff --git a/apps/gpspoilog/metadata.json b/apps/gpspoilog/metadata.json index 0a0902cea..41a917b79 100644 --- a/apps/gpspoilog/metadata.json +++ b/apps/gpspoilog/metadata.json @@ -6,7 +6,7 @@ "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial", "icon": "app.png", "tags": "outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "interface": "interface.html", "storage": [ {"name":"gpspoilog.app.js","url":"app.js"}, diff --git a/apps/gpssetup/metadata.json b/apps/gpssetup/metadata.json index b8b6dfc23..15a7c1cf2 100644 --- a/apps/gpssetup/metadata.json +++ b/apps/gpssetup/metadata.json @@ -6,7 +6,7 @@ "description": "Configure the GPS power options and store them in the GPS nvram", "icon": "gpssetup.png", "tags": "gps,tools,outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"gpssetup","url":"gpssetup.js"}, diff --git a/apps/jclock/ChangeLog b/apps/jclock/ChangeLog index c530930a6..e5146766c 100644 --- a/apps/jclock/ChangeLog +++ b/apps/jclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Created 0.02: Changed side bar color to blue for better clarity when it's locked +0.03: Minor updates on font size/color and Bluetooth indicator diff --git a/apps/jclock/README.md b/apps/jclock/README.md index 7ddb346dc..2d8c11838 100644 --- a/apps/jclock/README.md +++ b/apps/jclock/README.md @@ -18,8 +18,8 @@ I have used Rebble clock since I bought my Banglejs 2, and wanted to make my own - Update time and status every 1 minute ## Screenshots -![](jclock_screenshot_no_BT.png) -![](jclock_screenshot_BT.png) +![](screenshot_noBT.png) +![](screenshot_BT.png) ## Creator diff --git a/apps/jclock/app.js b/apps/jclock/app.js index 5c4d709d6..f07b6b283 100644 --- a/apps/jclock/app.js +++ b/apps/jclock/app.js @@ -17,7 +17,7 @@ var drawTimeout; // schedule a draw for the next minute function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { + drawTimeout = setTimeout(() => { drawTimeout = undefined; draw(); }, 60000 - (Date.now() % 60000)); @@ -26,43 +26,56 @@ function queueDraw() { const zeroPad = (num, places) => String(num).padStart(places, '0'); function draw() { - let barWidth = 64; + const barWidth = 64; let date = new Date(); - + let battColor = '#0ff'; + // queue next draw in one minute queueDraw(); - + // clean screen g.reset().clearRect(Bangle.appRect); - + // draw side bar in blue - g.setColor('#00f'); - g.fillRect(0, 0, barWidth, g.getHeight()); - + g.setColor('#000').fillRect(0, 0, barWidth, g.getHeight()); + // show time on the right - g.setColor(g.theme.fg); - g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getHours(),2), 120, 10); - g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getMinutes(),2), 120, g.getHeight()/2+10); - - // show date - g.setFont('Vector', 20).setFontAlign(0, -1).setColor('#fff'); - g.drawString(require("date_utils").dow(date.getDay(),1).toUpperCase(), barWidth/2, 3); - g.drawString(date.getDate(), barWidth/2, 28); - g.drawString(require("date_utils").month(date.getMonth()+1,1).toUpperCase(), barWidth/2, 53); - - // divider, place holder for any other info - g.drawString('=====', barWidth/2, 78); - + g.setColor(g.theme.fg).setFontAlign(0, 0).setFontKdamThmor(); + g.setColor('#11f').drawString(zeroPad(date.getHours(), 2), 120, g.getHeight() / 4 + 12); + g.setColor('#000').drawString(zeroPad(date.getMinutes(), 2), 120, g.getHeight() * 3 / 4 + 12); + + // day of week + g.setColor('#fff').setFontAlign(0, -1).setFont('Vector', 28).drawString(require("date_utils").dow(date.getDay(), 1).toUpperCase(), barWidth / 2 + 1, 3); + //g.setFont('Vector', 30).drawString(twoCharsDayOfWeek[date.getDay()], barWidth/2, 3); + + // month + g.drawString(require("date_utils").month(date.getMonth() + 1, 1).toUpperCase(), barWidth / 2 + 1, 84); + + // date + g.setColor('#0ff').setFont('Vector', 48).drawString(date.getDate(), barWidth / 2 + 5, 36); + // show daily steps - g.drawString(Bangle.getHealthStatus("day").steps, barWidth/2, 103); - + g.setFontAlign(1, -1).setColor('#fff').setFont('Vector', 24).drawString((Bangle.getHealthStatus("day").steps / 1000).toFixed(1) + 'k', barWidth, 125); + + // Bluetooth/GPS/Compass connection status + if (NRF.getSecurityStatus().connected) g.setColor('#0ff').fillRect(5, 115, barWidth - 5, 120); + // show battery remaining percentage - g.drawString(E.getBattery() + '%', barWidth/2, 153); - - // Bluetooth connection status - if (NRF.getSecurityStatus().connected) g.drawString('>BT<', barWidth/2, 128); + battColor = Bangle.isCharging() ? '#f00' : (E.getBattery() < 30 ? '#ff0' : '#0f0'); + g.setColor(battColor).drawString(E.getBattery() + '%', barWidth, 153); } -draw(); +function handleEvent() { + draw(); +} + +Bangle.on('charging', handleEvent); +NRF.on('connect', handleEvent); +NRF.on('disconnect', handleEvent); Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +require("widget_utils").hide(); +draw(); diff --git a/apps/jclock/jclock_screenshot_BT.png b/apps/jclock/jclock_screenshot_BT.png deleted file mode 100644 index e4eac99d0..000000000 Binary files a/apps/jclock/jclock_screenshot_BT.png and /dev/null differ diff --git a/apps/jclock/jclock_screenshot_no_BT.png b/apps/jclock/jclock_screenshot_no_BT.png deleted file mode 100644 index a312d23ed..000000000 Binary files a/apps/jclock/jclock_screenshot_no_BT.png and /dev/null differ diff --git a/apps/jclock/metadata.json b/apps/jclock/metadata.json index 9a7ddd337..830f5f2ba 100644 --- a/apps/jclock/metadata.json +++ b/apps/jclock/metadata.json @@ -2,12 +2,12 @@ "name":"jclock", "shortName":"jclock", "icon":"app.png", - "version":"0.02", + "version":"0.03", "description":"Similar layout to Rebble clock, but much simpler features with switched time and the feature window. The time is on the right side. This is my first Bangle app.", "type": "clock", "tags": "clock", "supports" : ["BANGLEJS2"], - "screenshots": [{"url":"jclock_screenshot_no_BT.png"},{"url":"jclock_screenshot_BT.png"}], + "screenshots": [{"url":"screenshot_noBT.png"},{"url":"screenshot_BT.png"}], "storage": [ {"name":"jclock.app.js","url":"app.js"}, {"name":"jclock.img","url":"app-icon.js","evaluate":true} diff --git a/apps/jclock/screenshot_BT.png b/apps/jclock/screenshot_BT.png new file mode 100644 index 000000000..0a68e9703 Binary files /dev/null and b/apps/jclock/screenshot_BT.png differ diff --git a/apps/jclock/screenshot_noBT.png b/apps/jclock/screenshot_noBT.png new file mode 100644 index 000000000..0973c35ce Binary files /dev/null and b/apps/jclock/screenshot_noBT.png differ