+ Select format: + + + + +
+ ++ Options: + + + + +
+ +Select watchface folder:
+or
+Select watchface zip file:
Download Demo Watchface here: + digitalretro.zip + simpleanalog.zip +
+ + + + + diff --git a/apps/imageclock/demomode.js b/apps/imageclock/demomode.js new file mode 100644 index 000000000..8c9d19195 --- /dev/null +++ b/apps/imageclock/demomode.js @@ -0,0 +1,29 @@ +var demostate = 0; +function demoMode(){ + lockedRedraw = 2000; + unlockedRedraw = 2000; + for (var c in multistates){ + multistates[c] = ()=>{return ["on","off"][demostate%2];}; + if (c == "WeatherTemperatureUnit") multistates[c] = ()=>{return ["celsius","fahrenheit"][demostate%2];}; + if (c == "Notifications") multistates[c] = ()=>{return ["on","off","vibrate"][demostate%3];}; + } + for (var c in numbers){ + if (c.contains("Minute")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + if (c.contains("Second")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + if (c.contains("Hour")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + } + for (var c in numbers){ + if (c.contains("Ones")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + if (c.contains("Tens")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + } + numbers.Pulse = ()=>{return Math.floor((Math.random() * 60) + 40);}; + numbers.Steps = ()=>{return Math.floor((Math.random() * 10000) + 10);}; + numbers.Temperature = ()=>{return Math.floor((Math.random() * 15) + 10);}; + numbers.Pressure = ()=>{return Math.floor((Math.random() * 1000) + 10);}; + numbers.Altitude = ()=>{return Math.floor((Math.random() * 1000) + 10);}; + numbers.WeatherCode = ()=>{return Math.floor((Math.random() * 800) + 100);}; + numbers.WeatherTemperature = ()=>{return Math.floor((Math.random() * 10) + 0);}; +} +demoMode(); +handleLock(false); +setInterval(()=>{demostate++;},1000); diff --git a/apps/imageclock/digitalretro.zip b/apps/imageclock/digitalretro.zip new file mode 100644 index 000000000..96ab90b6d Binary files /dev/null and b/apps/imageclock/digitalretro.zip differ diff --git a/apps/imageclock/metadata.json b/apps/imageclock/metadata.json new file mode 100644 index 000000000..a2594653e --- /dev/null +++ b/apps/imageclock/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "imageclock", + "name": "Imageclock", + "shortName": "Imageclock", + "version": "0.07", + "type": "clock", + "description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.", + "icon": "app.png", + "tags": "clock", + "supports": ["BANGLEJS2"], + "custom": "custom.html", + "customConnect": false, + "readme": "README.md", + "storage": [ + {"name":"imageclock.app.js","url":"app.js"}, + {"name":"imageclock.settings.js","url":"settings.js"}, + {"name":"imageclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/imageclock/settings.js b/apps/imageclock/settings.js new file mode 100644 index 000000000..a86901b9e --- /dev/null +++ b/apps/imageclock/settings.js @@ -0,0 +1,35 @@ +(function(back) { + var FILE = "imageclock.json"; + + var settings = Object.assign({ + stepsgoal: 10000, + perflog: false + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + '': { 'title': 'Imageclock' }, + '< Back': back, + 'Steps goal': { + value: settings.stepsgoal, + min: 0, + step: 500, + max: 50000, + onchange: v => { + settings.stepsgoal = v; + writeSettings(); + } + }, + 'Performance log': { + value: !!settings.perflog, + format: v => settings.perflog ? "On" : "Off", + onchange: v => { + settings.perflog = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/imageclock/simpleanalog.zip b/apps/imageclock/simpleanalog.zip new file mode 100644 index 000000000..1301be055 Binary files /dev/null and b/apps/imageclock/simpleanalog.zip differ diff --git a/apps/invader/ChangeLog b/apps/invader/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/invader/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/invader/README.md b/apps/invader/README.md new file mode 100644 index 000000000..7ed98defb --- /dev/null +++ b/apps/invader/README.md @@ -0,0 +1,23 @@ +# App Name + +Invader + +## Usage + +For fun! - I'm creating this demo to learn JavaScript with the Bangle.js 2 + +## Features + +Shoot the Alien, you have three lives + +## Controls + +Touch the lower Left or Right hand sides of the screen to move turret left or right, tap upper Right hand part of screen to fire, tap upper Left hand part of screen to restart + +## Requests + +bkumanchik on Espruino Forums + +## Creator + +Brian Kumanchik 2022 diff --git a/apps/invader/app-icon.js b/apps/invader/app-icon.js new file mode 100644 index 000000000..dc7003c84 --- /dev/null +++ b/apps/invader/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AH4AYgAACC/4X/C/YbTFbYv/F/4rTAC4v/F/4vfC5YnPGaYv/F/4vLc7b3TF/4v/F/4v/F/4vTA5YAPE64v/F/4fTa55DXF/4v/AH4A/AH4A/AH4A/AHIA==")) diff --git a/apps/invader/app.js b/apps/invader/app.js new file mode 100644 index 000000000..89e7462f6 --- /dev/null +++ b/apps/invader/app.js @@ -0,0 +1,452 @@ +// Brian Kumanchik +// Started 05-25-22 +// My Invader Demo, for Bangle.js 2, written JavaScript - using Espruino Web IDE + + +// note: resolution is 176x176 + + +// to do: +// upload to official app page +// make invader clock + + + +// - variables ----------------------------------------- +// invader variables +var inv_x = 77; +var inv_y = 20; +var i_anim_delay = 10; // invader animation (and move) delay +var inv_frame = 1; // invader start animation frame +var ix_speed = 6; // march speed +var i_dir = 1; // 1 = right, 0 = left +var been_hit = false; // invader hit state +// - shoot variables +var inv_shot_x = -32; +var inv_shot_y = -32; +var inv_fire_pause = 30; +var inv_fired = false; // invader fired state +// - explode variables +var been_hit = false; // invader hit state +var bx = -32; // blast x +var by = -32; // blast y +var blast_delay = 15; // invader blast delay - pause after explosion +var boom_play = false; + +// turret variables +var tur_x = 77; +var tur_y = 148; +var shot_fired = false; // turret fired state +var sx = -20; // turret shot starting x - off screen +var sy = -20; // turret shot starting y - off screen +var turret_been_hit = false; +var turret_blast_delay = 25; // keep blast active on screen for 60 frames +var turret_exp_frame = 1; // turret explode start animation frame +var turret_anim_delay = 3; // turret explode animation delay +var explosion_play = false; + +// misc variables +var score = 0; // starting score +var lives = 3; // starting lives +var game_state = 0; // game state - 0 = game not started, 1 = game running, 3 = game over +var ang = 0.1; +var start_been_pressed = false; // stops double press on restart +var fire_been_pressed = false; // stops auto fire + +// input(screen controller) variables +var BTNL, BTNR, BTNF, BTNS; // button - left, right, fire, start +var tap = {}; +// use tapping on screen for left, right, fire, start +Bangle.on('drag',e=>tap=e); +BTNL = { read : _=>tap.b && tap.x < 88 && tap.y > 88}; +BTNR = { read : _=>tap.b && tap.x > 88 && tap.y > 88}; +BTNF = { read : _=>tap.b && tap.x > 88 && tap.y < 88}; +BTNS = { read : _=>tap.b && tap.x < 88 && tap.y < 88}; + + +// - sprites ------------------------------------------- +// invader sprites +var invader_a = + require("heatshrink").decompress(atob("hcIwkBiIBBAQoECCQQFBgEQAIMBEhUBDoYWDAYI=")); +var invader_b = + require("heatshrink").decompress(atob("hcIwkBiIBBAQMQAoQEBgISCAYUQAIQAEB4YEBEAgEDAYIA==")); +var boom = + require("heatshrink").decompress(atob("hcJwkBiMQAIURgMQAgIKBAIICFAIMAAwIWBBAYSIEAgrDiA=")); +var inv_shot = + require("heatshrink").decompress(atob("gcFwkBiERiAABAYQ")); + +// turret sprites +var turret = + require("heatshrink").decompress(atob("h8IwkBiIABAYYACgAHFiEABggADCAInFgITBAAgOPA==")); +var tur_exp_a = + require("heatshrink").decompress(atob("h8IwkBiMRiACBAAwJEiAABBQgZCAAkAiAJBBoIUBgIABBgQACDIQ9ECQIA==")); +var tur_exp_b = + require("heatshrink").decompress(atob("h8IwkBiIBBAAUBiADCiMQAwQFDCIYXEB4IABgMAEYQXBiEAAQIQBAoIABDAQUCAAIVBA")); +var shot = + require("heatshrink").decompress(atob("gMDwkBAoIA==")); + + +// function to move and animate invader +function move_anim_inv() { + // invader anim code + i_anim_delay -= 1; + if ((i_anim_delay < 0) && !(been_hit)) { + i_anim_delay = 10; + + inv_frame += 1; + if (inv_frame > 2) { + inv_frame = 1; + } + + // move right + if (i_dir == 1){ + inv_x += ix_speed; + if (inv_x >= 142) { + inv_y += 8; // step down + i_dir = -1; + } + } + + // move left + if (i_dir < 1){ + inv_x -= ix_speed; + if (inv_x <= 10) { + inv_y += 8; // step down + i_dir = 1; + } + } + } +} + + +// function to make invader fire +function invader_fire() { + inv_fire_pause -= 1; + + if (!(inv_fired)) { // so once invader shot is fired it doesn't follow the invader still + inv_shot_x = inv_x + 8; + inv_shot_y = inv_y + 18; + } + + if (inv_fire_pause < 0) { + inv_fired = true; + inv_shot_y += 8; + } +} + + +// function to make turret explode (when hit) then start back in center +function turret_hit() { + if (turret_been_hit) { + if (!(explosion_play)) { + //Bangle.buzz(); + //Bangle.beep(); + } + + explosion_play = true; + turret_anim_delay -= 1; + turret_blast_delay -= 1; + + if (turret_anim_delay < 0) { + turret_exp_frame += 1; + if (turret_exp_frame > 2) { + Bangle.buzz(); + turret_exp_frame = 1; + } + turret_anim_delay = 3; + } + + if (turret_blast_delay < 0) { + turret_blast_delay = 21; + turret_been_hit = false; + explosion_play = false; + tur_x = 77; // reset turret x + tur_y = 148; // reset turret y + } + } +} + + +// function to make invader explode (when hit) then randomly start somewhere else +function invader_hit() { + if (been_hit) { + if (!(boom_play)) { + Bangle.buzz(); + //Bangle.beep(); + } + + inv_shot_x = -32; // hide shot + inv_shot_y = -32; // hide shot + inv_fire_pause = 30; // and reset pause + + boom_play = true; + blast_delay -= 1; + + if (blast_delay < 0) { + blast_delay = 15; + boom_play = false; + been_hit = false; + bx = -32; // move boom off screen (following invader) + by = -32; + // generate a random rounded number between 10 and 142; + inv_x = Math.floor(Math.random() * 142) + 10; + inv_y = 20; // move invader back up after being hit + i_dir = 1; // reset invader direction + } + } +} + + +// - setup stuff --------------------------------------- +function gameStart() { + setInterval(onFrame, 50); +} + + +// - main loop ------------------------------------------------------------- +function onFrame() { + + // game not started state (title screen) *************************** + if(game_state == 0) { + g.clear(); + + + if (!(BTNS.read())) { + start_been_pressed = false; // stops double press on restart + } + + + // draw text during game over state + g.setFont("4x6", 4); // set font and size x 2 + g.setColor(0,1,0); // set color (black) + g.drawString("INVADER", 33, 55); + + + // just animate invader + // invader anim code + i_anim_delay -= 1; + if(i_anim_delay < 0) { + i_anim_delay = 15; + + inv_frame += 1; + if (inv_frame > 2) { + inv_frame = 1; + } + } + + + // draw sprites during game over state + // next 2 line for a rotating invader on the title screen + //ang += 0.1; + //g.drawImage(invader_a, 88, 98, {scale:4, rotate:ang}); + if(inv_frame == 1) { + g.drawImage(invader_a, 88-22, 85, {scale:4}); + } + else if(inv_frame == 2) { + g.drawImage(invader_b, 88-22, 85, {scale:4}); + } + + // reset stuff + if(BTNS.read() && !(start_been_pressed)) { + turret_been_hit = false; + tur_x = 77; // reset turret to center of screen + tur_y = 148; // reset turret y + inv_x = 77; // reset invader to center of screen + inv_y = 20; // reset invader back to top + i_dir = 1; // reset invader direction + lives = 3; // reset lives + score = 0; // reset score + explosion_play = false; + game_state = 1; + turret_blast_delay = 25; + } + + + g.flip(); + } + + + // game over state ************************************************* + if(game_state == 3) { + g.clear(); + + // draw text during game over state + g.setFont("4x6", 2); // set font and size x 2 + g.setColor(0,0,0); // set color (black) + g.drawString("SCORE:" + score ,5, 5); + g.drawString("LIVES:" + lives ,117, 5); + g.drawString("GAME OVER", 52, 80); + + + // draw sprites during game over state + // - invader frame 2 + g.drawImage(invader_b, inv_x, inv_y, {scale:2}); + g.drawImage(tur_exp_b, tur_x, tur_y, {scale:2}); + g.drawImage(inv_shot, inv_shot_x, inv_shot_y, {scale:2}); + + + // reset stuff + if(BTNS.read()) { + //turret_been_hit = false; + //tur_x = 77; // reset turret to center of screen + //tur_y = 148; // reset turret y + //inv_x = 77; // reset invader to center of screen + //inv_y = 20; // reset invader back to top + //i_dir = 1; // reset invader direction + //lives = 3; // reset lives + //score = 0; // reset score + //explosion_play = false; + game_state = 0; + start_been_pressed = true; + //turret_blast_delay = 25; + } + + + g.flip(); + } + + + // not game over state (game running) ****************************** + if(game_state == 1) { + Bangle.setLCDPower(1); // optional - this keeps the watch LCD lit up + g.clear(); + + + if (!(BTNF.read())) { + fire_been_pressed = false; // stops auto fire + } + + + // call function to move and animate invader + move_anim_inv(); + + // call function to make invader fire + invader_fire(); + + + // check input (screen presses) + if(BTNL.read() && tur_x >= 12 && !(turret_been_hit)) { + tur_x -= 6; + } + else if(BTNR.read() && tur_x <= 140 && !(turret_been_hit)) { + tur_x += 6; + } + else if(BTNF.read() && !(turret_been_hit) && !(fire_been_pressed) && !(shot_fired)) { + shot_fired = true; + fire_been_pressed = true; // stops auto fire + sx=tur_x + 12; + sy=tur_y - 7; + } + + + // check for turret shot going off screen before allowing to fire again + if (shot_fired) { + sy -= 8; + if (sy < 22) { + shot_fired = false; + sx = -32; + sy = -32; + } + } + + + // check for invader shot going off screen before allowing to fire again + if (inv_shot_y > 150 + ) { + inv_fired = false; + inv_shot_x = inv_x - 1; + inv_shot_y = inv_y + 7; + inv_fire_pause = 30; + } + + + // check for turret shot and invader collision + if ((sx >= inv_x) && (sx <= inv_x + 20) && (sy <= inv_y + 14)) { + sx = -32; + sy = -32; + been_hit = true; + score += 10; + } + + + // check for invader shot and turret collision + if ((inv_shot_x + 4) >= (tur_x) && (inv_shot_x) <= (tur_x + 24) && (inv_shot_y + 8) >= (tur_y + 6)) { + if (!(turret_been_hit)) { + lives -= 1; + + if (lives == 0) { + game_state = 3; + Bangle.buzz(); + } + turret_been_hit = true; + } + } + + + // - draw sprites ---------------------------------- + // invader sprites + if(!(been_hit)) { + if(inv_frame == 1) { + // - invader frame 1 + g.drawImage(invader_a, inv_x, inv_y, {scale:2}); + } + else if(inv_frame == 2) { + // - invader frame 2 + g.drawImage(invader_b, inv_x, inv_y, {scale:2}); + } + } + else { + // - invader explosion + g.drawImage(boom, inv_x, inv_y, {scale:2}); + } + // - invader shot + if (inv_fired) { + g.drawImage(inv_shot, inv_shot_x, inv_shot_y, {scale:2}); + } + else { + g.drawImage(inv_shot, -32, -32, {scale:2}); + } + + // turret sprites + if(!(turret_been_hit)) { + // - undamaged turret + g.drawImage(turret, tur_x, tur_y, {scale:2}); + } + else { + if(turret_exp_frame == 1) { + // - turret explosion frame 1 + g.drawImage(tur_exp_a, tur_x, tur_y, {scale:2}); + } + else if(turret_exp_frame == 2) { + // - turret explosion frame 2 + g.drawImage(tur_exp_b, tur_x, tur_y, {scale:2}); + } + } + // - turret shot + g.drawImage(shot, sx, sy, {scale:2}); + + + // call function to make invader explode then randomly start somewhere else + invader_hit(); + + + // call function to make turret explode (when hit) then start back in center + turret_hit(); + + + // - draw text ------------------------------------- + g.setFont("4x6", 2); // set font and size x 2 + g.setColor(0,0,0); // set color (black) + g.drawString("SCORE:" + score ,5,5); + g.drawString("LIVES:" + lives ,117,5); + + + g.flip(); + } + +} // end main loop --------------------------------------------------------- + + +gameStart(); + + diff --git a/apps/invader/app.png b/apps/invader/app.png new file mode 100644 index 000000000..3b9f82205 Binary files /dev/null and b/apps/invader/app.png differ diff --git a/apps/invader/metadata.json b/apps/invader/metadata.json new file mode 100644 index 000000000..bb74f5122 --- /dev/null +++ b/apps/invader/metadata.json @@ -0,0 +1,15 @@ +{ "id": "invader", + "name": "Invader", + "shortName":"Invader", + "version":"0.11", + "description": "A Space Invader game-like demo - work in progress", + "icon": "app.png", + "screenshots" : [ { "url":"screenshot_0.png" }, { "url":"screenshot_1.png" }, { "url":"screenshot_2.png" } ], + "tags": "game", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"invader.app.js","url":"app.js"}, + {"name":"invader.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/invader/screenshot.png b/apps/invader/screenshot.png new file mode 100644 index 000000000..ae936f6c7 Binary files /dev/null and b/apps/invader/screenshot.png differ diff --git a/apps/invader/screenshot_0.png b/apps/invader/screenshot_0.png new file mode 100644 index 000000000..804f3e435 Binary files /dev/null and b/apps/invader/screenshot_0.png differ diff --git a/apps/invader/screenshot_1.png b/apps/invader/screenshot_1.png new file mode 100644 index 000000000..14164bc6e Binary files /dev/null and b/apps/invader/screenshot_1.png differ diff --git a/apps/invader/screenshot_2.png b/apps/invader/screenshot_2.png new file mode 100644 index 000000000..5f6ade79c Binary files /dev/null and b/apps/invader/screenshot_2.png differ diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 67c2f903a..4b577e191 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -51,4 +51,5 @@ 0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) 0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items 0.38: Add telegram foss handling -0.39: Don't turn on the screen after unread timeout expires (#1873) +0.39: Set default color for message icons according to theme + Don't turn on the screen after unread timeout expires (#1873) diff --git a/apps/messages/app.js b/apps/messages/app.js index 46f010c0b..d4540b797 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -317,7 +317,7 @@ function showMessage(msgid) { {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, ]}, - { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg), pad: 3, cb:()=>{ + { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg, g.theme.fg2), pad: 3, cb:()=>{ cancelReloadTimeout(); // don't auto-reload to clock now showMessageSettings(msg); }}, diff --git a/apps/rgb/ChangeLog b/apps/rgb/ChangeLog new file mode 100644 index 000000000..a08433b7c --- /dev/null +++ b/apps/rgb/ChangeLog @@ -0,0 +1 @@ +0.01: initial release diff --git a/apps/rgb/README.md b/apps/rgb/README.md new file mode 100644 index 000000000..5723f99c6 --- /dev/null +++ b/apps/rgb/README.md @@ -0,0 +1,16 @@ +rgb +=== + +A simple RGB color selector utility for the BangleJS2 smartwatch. + +Features a vector toggle widget and swipe interactivity. + +Author +------ + +Written by pancake in 2022 + +Screenshots +----------- + + diff --git a/apps/rgb/app-icon.js b/apps/rgb/app-icon.js new file mode 100644 index 000000000..9919a4365 --- /dev/null +++ b/apps/rgb/app-icon.js @@ -0,0 +1 @@ +atob("MDCI/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tGxsbGxsbBISEhISEhISEhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tGxsbGxsbBISEhISEhISEhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tGxsbGxsbBISEhISEhISEhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/g==") diff --git a/apps/rgb/app.js b/apps/rgb/app.js new file mode 100644 index 000000000..aa18d93ae --- /dev/null +++ b/apps/rgb/app.js @@ -0,0 +1,133 @@ +const rgb = [0, 0, 0]; +const w = g.getWidth(); +const h = g.getHeight(); +function drawToggle (value, x, y, options) { + if (!options) options = {}; + if (!options.scale) options.scale = 1; + const h = (options.scale * 16); + const h2 = h / 2; + const w = (options.scale) * 32; + + g.setColor(0.3, 0, 0.3); + g.fillCircle(x + h2, y + h2, h2 - 1); + g.fillCircle(x + w - h2, y + h2, h2 - 1); + g.fillRect(x + h2, y, x + w - h2, y + h); + + y += 4; + g.setColor(0.6, 0.6, 0.6); + g.fillCircle(x + h2, y + h2 + 2, h2 - 1); + g.fillCircle(x + w - h2, y + h2 + 2, h2 - 1); + g.fillRect(x + h2, y + 2, x + w - h2, y + h + 1); + + if (value) { + x += w - h; + } + g.setColor(0, 0.5, 0); + g.fillCircle(x + h2, y + h2 + 2, h2 - 1); + y -= 4; + if (colorMode) { + g.setColor(0, 1, 0); + } else { + g.setColor(0.5, 0.5, 0.5); + } + g.fillCircle(x + h2, y + h2 + 2, h2 - 1); + + g.setColor(0, 0.8, 0); + g.fillCircle(x + h2 - 2, y + h2, h2 - 8); + if (colorMode) { + g.setColor(0, 1, 0); + } else { + g.setColor(0.5, 0.5, 0.5); + } g.fillCircle(x + h2 + 4, y + h2 + 4, h2 - 9); +} + +function refresh () { + g.setBgColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); + g.clear(); + g.setColor(1, 1, 1); + g.setFont12x20(1); + g.setColor(0, 0, 0); + let s = '#' + hex[(rgb[0] >> 4) & 0xf] + hex[rgb[0] & 0xf]; + s += hex[(rgb[1] >> 4) & 0xf] + hex[rgb[1] & 0xf]; + s += hex[(rgb[2] >> 4) & 0xf] + hex[rgb[2] & 0xf]; + g.setColor(1, 0, 1); + g.fillRect(0, 0, w, 32); + g.setColor(0.2, 0, 1); + g.fillRect(0, 32, w, 33); + g.setColor(1, 1, 1); + g.drawString(s, 8, 8); + // drawToggle (colorMode, w - 8 - 32*(rgb[0]/50), 8 + 255- rgb[2], {scale:1 * rgb[0] / 50}); + drawToggle(colorMode, w - 40, 4, { scale: 1.2 }); + + if (colorMode) { + g.setColor(1, 0, 0); + g.fillRect(0, h, w / 3, h - 32); + g.setColor(0, 1, 0); + g.fillRect(w / 3, h, w - (w / 3), h - 32); + g.setColor(0, 0, 1); + g.fillRect(w - (w / 3), h, w, h - 32); + + g.setColor(0.5, 0, 0); + g.fillRect(0, h - 33, w / 3, h - 34); + g.setColor(0, 0.5, 0); + g.fillRect(w / 3, h - 33, w - (w / 3), h - 34); + g.setColor(0, 0, 0.5); + g.fillRect(w - (w / 3), h - 33, w, h - 34); + } else { + g.setColor(0.5, 0.5, 0.5); + g.fillRect(0, h, w, h - 32); + g.setColor(0.2, 0.2, 0.2); + g.fillRect(0, h - 33, w, h - 34); + } + // column lines + function f (x) { + const s = '' + (rgb[x] / 255); + return s.substring(0, 4); + } + g.setColor(1, 1, 1); + g.drawLine(w / 3, h, w / 3, h / 2); + g.drawLine(w - (w / 3), h, w - (w / 3), h / 2); + g.setFont6x15(2); + g.drawString(f(0), 8, h - 28); + g.drawString(f(1), 8 + (w / 3), h - 28); + g.drawString(f(2), 8 + (2 * w / 3), h - 28); +} +let k = -1; +var colorMode = true; +Bangle.on('touch', function (wat, tap) { + if (tap.x > w / 2 && tap.y < 32) { + colorMode = !colorMode; + refresh(); + } +}); + +function deltaComponent (k, dy) { + rgb[k] -= dy; + if (rgb[k] > 255) { + rgb[k] = 255; + } else if (rgb[k] < 0) { + rgb[k] = 0; + } +} +Bangle.on("button", function() { + rgb[0] = rgb[1] = rgb[2] = 127; +}); +Bangle.on('drag', function (tap, top) { + if (colorMode) { + if (tap.x < w / 3) { + k = 0; + } else if (tap.x > (w - (w / 3))) { + k = 2; + } else { + k = 1; + } + deltaComponent(k, tap.dy); + } else { + deltaComponent(0, tap.dy); + deltaComponent(1, tap.dy); + deltaComponent(2, tap.dy); + } + refresh(); +}); +refresh(); + diff --git a/apps/rgb/app.png b/apps/rgb/app.png new file mode 100644 index 000000000..e9210d2b6 Binary files /dev/null and b/apps/rgb/app.png differ diff --git a/apps/rgb/metadata.json b/apps/rgb/metadata.json new file mode 100644 index 000000000..7a47e65b9 --- /dev/null +++ b/apps/rgb/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "rgb", + "name": "rgb", + "shortName": "rgb", + "version": "0.01", + "type": "app", + "description": "RGB utility", + "icon": "app.png", + "allow_emulator": true, + "tags": "tools", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "rgb.app.js", + "url": "app.js" + }, + { + "name": "rgb.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.png" + } + ] +} diff --git a/apps/rgb/screenshot.png b/apps/rgb/screenshot.png new file mode 100644 index 000000000..1815cd492 Binary files /dev/null and b/apps/rgb/screenshot.png differ diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index ea2ecd02b..1513194fe 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -52,3 +52,4 @@ 0.46: Fix regression after making 'calibrate battery' only for Bangle.js 2 0.47: Allow colors to be translated Improve "Turn Off" user experience +0.48: Allow reading custom themes from files diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index ce4e5b337..17519730e 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.47", + "version": "0.48", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 39cf6c1d1..3bb9b4e22 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -221,7 +221,8 @@ function showThemeMenu() { Bangle.drawWidgets(); m.draw(); } - var m = E.showMenu({ + + var themesMenu = { '':{title:/*LANG*/'Theme'}, '< Back': ()=>showSystemMenu(), /*LANG*/'Dark BW': ()=>{ @@ -239,9 +240,26 @@ function showThemeMenu() { fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); - }, - /*LANG*/'Customize': ()=>showCustomThemeMenu(), - }); + } + }; + + require("Storage").list(/^.*\.theme$/).forEach( + n => { + let newTheme = require("Storage").readJSON(n); + themesMenu[newTheme.name ? newTheme.name : n] = () => { + upd({ + fg:cl(newTheme.fg), bg:cl(newTheme.bg), + fg2:cl(newTheme.fg2), bg2:cl(newTheme.bg2), + fgH:cl(newTheme.fgH), bgH:cl(newTheme.bgH), + dark:newTheme.dark + }); + }; + } + ); + + themesMenu[/*LANG*/'Customize'] = () => showCustomThemeMenu(); + + var m = E.showMenu(themesMenu); function showCustomThemeMenu() { function setT(t, v) { diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 02658296a..036bdb0f4 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fix position and overdraw bugs 0.03: Better memory usage, theme support 0.04: Replace the 8 phases by a more exact drawing, see forum.espruino.com/conversations/371985 +0.05: Fixed the algorithm for calculating the moon's phase diff --git a/apps/widmp/metadata.json b/apps/widmp/metadata.json index 94f05a426..ff7ad79ad 100644 --- a/apps/widmp/metadata.json +++ b/apps/widmp/metadata.json @@ -1,7 +1,7 @@ { "id": "widmp", "name": "Moon Phase Widget", - "version": "0.04", + "version": "0.05", "description": "Display the current moon phase in blueish for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", "icon": "widget.png", "type": "widget", diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index 6da572aab..bf032a5ff 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,19 +1,24 @@ WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; var southernHemisphere = false; // when in southern hemisphere, use the "My Location" App + var lastCalculated = 0; // When we last calculated the phase + var phase = 0; // The last phase we calculated const simulate = false; // simulate one month in one minute const updateR = 1000; // update every x ms in simulation - function moonPhase() { - const d = Date(); - var month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); - if (simulate) day = d.getSeconds() / 2 +1; - if (month < 3) {year--; month += 12;} - mproz = ((365.25 * year + 30.6 * ++month + day - 694039.09) / 29.5305882); - mproz = mproz - (mproz | 0); // strip integral digits, result is between 0 and <1 - if (simulate) console.log(mproz + " " + day); - return (mproz); + // https://deirdreobyrne.github.io/calculating_moon_phases/ + function moonPhase(millis) { + k = (millis - 946728000000) / 3155760000000; + mp = (8328.69142475915 * k) + 2.35555563685; + m = (628.30195516723 * k) + 6.24006012726; + d = (7771.37714483372 * k) + 5.19846652984; + t = d + (0.109764 * Math.sin (mp)) - (0.036652 * Math.sin(m)) + (0.022235 * Math.sin(d+d-mp)) + (0.011484 * Math.sin(d+d)) + (0.003735 * Math.sin(mp+mp)) + (0.00192 * Math.sin(d)); + k = (1 - Math.cos(t))/2; + if (Math.sin(t) < 0) { + k = -k; + } + return (k); // Goes 0 -> 1 for waxing, and from -1 -> 0 for waning } function loadLocation() { @@ -44,11 +49,19 @@ WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { g.fillRect(CenterX - Radius, CenterY - Radius, CenterX + Radius, CenterY + Radius); g.setColor(0x41f); - mproz = moonPhase(); // mproz = 0..<1 + millis = (new Date()).getTime(); + if ((millis - lastCalculated) >= 7200000) { + phase = moonPhase(millis); + lastCalculated = millis; + } - leftFactor = mproz * 4 - 1; - rightFactor = (1 - mproz) * 4 - 1; - if (mproz >= 0.5) leftFactor = 1; else rightFactor = 1; + if (phase < 0) { // waning - phase goes from -1 to 0 + leftFactor = 1; + rightFactor = -1 - 2*phase; + } else { // waxing - phase goes from 0 to 1 + rightFactor = 1; + leftFactor = -1 + 2*phase; + } if (true == southernHemisphere) { var tmp=leftFactor; leftFactor=rightFactor; rightFactor=tmp; } diff --git a/apps/widshipbell/metadata.json b/apps/widshipbell/metadata.json new file mode 100644 index 000000000..c130b04ee --- /dev/null +++ b/apps/widshipbell/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widshipbell", + "name": "Ship's bell Widget", + "shortName": "Ship's bell", + "version": "0.01", + "description": "A widget that buzzes according to a nautical bell, one strike at 04:30, two strikes at 05:00, up to eight strikes at 08:00 and so on.", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widshipbell.wid.js","url":"widget.js"}, + {"name":"widshipbell.settings.js","url":"settings.js"} + ], + "data": [{"name":"widshipbell.json"}] +} diff --git a/apps/widshipbell/settings.js b/apps/widshipbell/settings.js new file mode 100644 index 000000000..bb47e9b20 --- /dev/null +++ b/apps/widshipbell/settings.js @@ -0,0 +1,27 @@ +(function(back) { + var FILE = "widshipbell.json"; + // Load settings + var settings = Object.assign({ + strength: 1, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Ship's bell" }, + "< Back" : () => back(), + 'Strength': { + value: settings.strength, + min: 0, max: 2, + format: v => ["Off", "Weak", "Strong"][v], + onchange: v => { + settings.strength = v; + writeSettings(); + } + }, + }); +}) + diff --git a/apps/widshipbell/widget.js b/apps/widshipbell/widget.js new file mode 100644 index 000000000..e37edb6fd --- /dev/null +++ b/apps/widshipbell/widget.js @@ -0,0 +1,52 @@ +(() => { + const strength = Object.assign({ + strength: 1, + }, require('Storage').readJSON("widshipbell.json", true) || {}).strength; + + function replaceAll(target, search, replacement) { + return target.split(search).join(replacement); + } + + function check() { + const now = new Date(); + const currentMinute = now.getMinutes(); + const currentSecond = now.getSeconds(); + const etaMinute = 30-(currentMinute % 30); + + if (etaMinute === 30 && currentSecond === 0) { + const strikeHour = now.getHours() % 4; + // buzz now + let pattern=''; + if (strikeHour === 0 && currentMinute == 0) { + pattern = '.. .. .. ..'; + } else if (strikeHour === 0 && currentMinute === 30) { + pattern = '.'; + } else if (strikeHour === 1 && currentMinute === 0) { + pattern = '..'; + } else if (strikeHour === 1 && currentMinute === 30) { + pattern = '.. .'; + } else if (strikeHour === 2 && currentMinute === 0) { + pattern = '.. ..'; + } else if (strikeHour === 2 && currentMinute === 30) { + pattern = '.. .. .'; + } else if (strikeHour === 3 && currentMinute === 0) { + pattern = '.. .. ..'; + } else if (strikeHour === 3 && currentMinute === 30) { + pattern = '.. .. .. .'; + } + pattern = replaceAll(pattern, ' ', ' '); // 4x pause + pattern = replaceAll(pattern, '.', '. '); // pause between bells + if (strength === 2) { // strong selected + pattern = replaceAll(pattern, '.', ':'); + } + require("buzz").pattern(pattern); + } + + const etaSecond = etaMinute*60-currentSecond; + setTimeout(check, etaSecond*1000); + } + + if (strength !== 0) { + check(); + } +})(); diff --git a/apps/widshipbell/widget.png b/apps/widshipbell/widget.png new file mode 100644 index 000000000..891679f7d Binary files /dev/null and b/apps/widshipbell/widget.png differ