diff --git a/apps.json b/apps.json index 3cafdae78..5939740ef 100644 --- a/apps.json +++ b/apps.json @@ -118,6 +118,24 @@ {"name":"welcome.json"} ] }, + { "id": "mywelcome", + "name": "Customised Welcome", + "shortname": "My Welcome", + "icon": "app.png", + "version":"0.09", + "description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting", + "tags": "start,welcome", + "custom":"custom.html", + "storage": [ + {"name":"mywelcome.boot.js","url":"boot.js"}, + {"name":"mywelcome.app.js","url":"app.js"}, + {"name":"mywelcome.settings.js","url":"settings.js"}, + {"name":"mywelcome.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"mywelcome.json"} + ] + }, { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", diff --git a/apps/mywelcome/ChangeLog b/apps/mywelcome/ChangeLog new file mode 100644 index 000000000..9545dbbfa --- /dev/null +++ b/apps/mywelcome/ChangeLog @@ -0,0 +1,13 @@ +0.01: New App! +0.02: Animate balloon intro +0.03: BTN3 now won't restart when at the end +0.04: Fix regression after tweaks to Storage.readJSON +0.05: Move configuration into App/widget settings +0.06: Move loader into welcome.boot.js +0.07: Run again when updated + Don't run again when settings app is updated (or absent) + Add "Run Now" option to settings +0.08: Don't overwrite existing settings on app update +0.09: Allow welcome to run after a fresh install + More useful app menu + BTN2 now goes to menu on release diff --git a/apps/mywelcome/app-icon.js b/apps/mywelcome/app-icon.js new file mode 100644 index 000000000..5c1373e17 --- /dev/null +++ b/apps/mywelcome/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap")) diff --git a/apps/mywelcome/app.js b/apps/mywelcome/app.js new file mode 100644 index 000000000..fcd193ce2 --- /dev/null +++ b/apps/mywelcome/app.js @@ -0,0 +1,298 @@ +// exec each function from seq one after the other +function animate(seq,period) { + var i = setInterval(function() { + if (seq.length) { + var f = seq.shift(); + if (f) f(); + } else clearInterval(i); + },period); +} + +// Fade in to FG color with angled lines +function fade(callback) { + var n = 0; + function f() { + for (var i=n;i<240;i+=10) { + g.drawLine(i,0,0,i); + g.drawLine(i,240,240,i); + } + g.flip(); + n++; + if (n<10) setTimeout(f,0); + else callback(); + } + f(); +} + + +var scenes = [ + function() { + g.clear(1); + eval(require("Storage").read("mywelcome.custom.js")); + },function() { + g.clear(1); + g.setFont("4x6",2); + var n=0; + var i = setInterval(function() { + n+=0.04; + g.setColor(n,n,n); + g.drawImage(Bangle.getLogo(),(240-222)/2,(240-100)/2); + if (n>=1) { + clearInterval(i); + setTimeout(()=>g.drawString("Open",34,144), 500); + setTimeout(()=>g.drawString("Hackable",34,156), 1000); + setTimeout(()=>g.drawString("Smart Watch",34,168), 1500); + } + },50); + },function() { + var img = require("heatshrink").decompress(atob("ptRxH+qYAfvl70mj5gAC0ekvd8FkAAdz3HJAYAH4+eJXWkJJYAF0hK2vfNJaIAB5t7S3fN5/V6wAD6vOTg9SumXy2W3QAB3eXul2JdnO63XAApPEVYvAJQIACJoRQDzBLoJQ3W5/NIwr4GJohMFAAROgJYvVJQiPGABZNN3bsdvYyESwnWJSIAC3RNM3V1JjZAES4nVJSYAB4xMNJrbkE56WD5xLVdB5NbFofNJbgABJh26qREPrFXrlbAAWjFgfWJgRLaTQhMLy5KNJINhsJLDrYrD5xLC6pLa5nGTR7oLq9bJQJMKTAXWJbbnR3RLJSoRMHv4pC5rkec6SaIrBLGw2r2XW1epcoqYeJiOXJYziEsOH2RBBw7lF56Yg5nGc6FScZOGJQPX2TmDFIfVTEBMSc4hLEw5KB6+rsJMH63X6pMf5hMQzBLCq5LD1ZLEJhTlfJiWXTA2GJYpMIcwPNc2O6TAuGRIPX1igDJg/PJmyYDcgXWwxMH1ApC53XcsHAJiVYcg2HJYZME0YpC5vWJkhLNJgLlDTAeFJhF/FQfVJkG6JiGXcomyJgOrJYhMErYqD53NJj7lRzBMDcoeGJhzoBJb3GJiN1qZBCJgWyJYpNF1LigAAXAJiNSJgzlGJgt/JkZLRy9TJgeHJhznFcuSZGw5MHJomjcuhLBqdcJiSaiTChMV1CYxy5LCqdXIAWy6+rJhCalTCN2JgdYH4WHJiGpTF7kDc43W2RMJTUZLQzBLFc4mr6+GJh2jTFmXJYyaEwuyc5Sag4xLZTQmG2WFJhxNaJYZMLJZSaEJoOHTR9/Ja+6JbdTqRNETRRNF1JLV4BLcAANYI5ToK1BLYJhWYJZwABq5NoJZ91JaAABdAZNS0ZLey9SJaRNYv5KM426JZmXuxKUJrKcL0lTzBLKzBKYJrVXvfGSol7EYWXJI27zF1JLQADq5NUrgYB4wAEEIV0comXI7wAFrCcPJgYWBTIIAETIN2JYmWuhMkdSdYCgOeJgueqRLFyzhfTi9bq4TC45MF49TuuXJlpONcogAC0hKB0gHDvZMEqRMpAANSq9crlbJAYADqwRDxGk0mIA4eCTQOeveXJdYAHqxNFdAeIAAQGCrOI0oHEAGVXTRJMGvgGCwRM7TAZMHwQGCvhM1rBMERIhMGAwdZJmtSqVTwNcwJEDJg19cvIADa4d9JhANDJnSLHJgrl6AAhFFAwpZDegjn7vhMGcvwABrJAFJgjl/TQpBBI4jl/AAN8TQhHDcv4ADcJBMDvpM+IYaeDAAhL+qd9SgycEJn7iEAA18Jf7nEcv4AIrJLIcv6aMcv4ADvhMHrJJ/AAbl/c6ZM/AAt9cv7nSIv7nLcv4AHrLl/TRpJBvgnjA==")); + g.reset(); + g.setColor("#6633ff"); + g.setBgColor("#6633ff"); + var y = 240, speed = 5; + function balloon(callback) { + y-=speed; + var x = (240-77)/2; + g.drawImage(img,x,y); + g.clearRect(x,y+81,x+77,y+81+speed); + if (y>60) setTimeout(balloon,0,callback); + else callback(); + } + fade(function() { + balloon(function() { + g.setColor(-1); + g.setFont("6x8",3); + g.setFontAlign(0,0); + g.drawString("Welcome.",120,160); + }); + }); + setTimeout(function() { + var n=0; + var i = setInterval(function() { + n+=5; + g.scroll(0,-5); + if (n>170) + clearInterval(i); + },20); + },3500); + + },function() { + g.reset(); + g.setBgColor("#ffa800");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 80, y = 35, h=35; + animate([ + ()=>g.drawString("Your",x,y+=h), + ()=>g.drawString("Bangle.js",x,y+=h), + ()=>g.drawString("has",x,y+=h), + ()=>g.drawString("3 buttons",x,y+=h), + ()=>{g.setFont("Vector",36);g.drawString("1",200,40);}, + ()=>g.drawString("2",200,120), + ()=>g.drawString("3",200,200) + ],200); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("1",200,40); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("Move up\nin menus\n\nTurn Bangle.js on\nif it was off", 20,40); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("2",200,120); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("Select menu\nitem\n\nLaunch app\nwhen watch\nis showing", 20,70); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("3",200,200); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("Move down\nin menus\n\nLong press\nto exit app\nand go back\nto clock", 20,100); + }, + function() { + g.reset(); + g.setBgColor("#ff3300");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",48); + g.drawString("1",200,40); + g.drawString("2",200,120); + g.setFontAlign(-1,-1); + g.setFont("6x8",2); + g.drawString("If Bangle.js\never stops,\nhold buttons\n1 and 2 for\naround six\nseconds.\n\n\n\nBangle.js will\nthen reboot.", 20,20); + }, + function() { + g.reset(); + g.setBgColor("#00a8ff");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 120, y = 10, h=21; + animate([ + ()=>{g.drawString("Bangle.js has a",x,y+=h); + g.drawString("simple touchscreen",x,y+=h);}, + 0,0, + ()=>{g.drawString("It'll detect touch",x,y+=h*2); + g.drawString("on left and right",x,y+=h);}, + 0,0, + ()=>{g.drawString("Horizontal swipes",x,y+=h*2); + g.drawString("work too. Try now",x,y+=h); + g.drawString("to change page.",x,y+=h);} + ],300); + }, + function() { + g.reset(); + g.setBgColor("#339900");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 120, y = 10, h=21; + animate([ + ()=>{g.drawString("Bangle.js",x,y+=h); + g.drawString("comes with",x,y+=h); + g.drawString("a few simple",x,y+=h); + g.drawString("apps installed",x,y+=h);}, + 0,0, + ()=>{g.drawString("To add more, visit",x,y+=h*2); + g.drawString("banglejs.com/apps",x,y+=h); + g.drawString("with a Bluetooth",x,y+=h); + g.drawString("capable device",x,y+=h);}, + ],400); + }, + function() { + g.reset(); + g.setBgColor("#990066");g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0); + var x = 120, y = 10, h=21; + g.drawString("You can also make",x,y+=h); + g.drawString("your own apps!",x,y+=h); + y=160; + g.drawString("Check out",x,y+=h); + g.drawString("banglejs.com",x,y+=h); + + var rx = 0, ry = 0; + var h = Graphics.createArrayBuffer(96,96,1,{msb:true}); + // draw a cube + function draw() { + // rotate + rx += 0.1; + ry += 0.11; + var rcx=Math.cos(rx), + rsx=Math.sin(rx), + rcy=Math.cos(ry), + rsy=Math.sin(ry); + // Project 3D coordinates into 2D + function p(x,y,z) { + var t; + t = x*rcy + z*rsy; + z = z*rcy - x*rsy; + x=t; + t = y*rcx + z*rsx; + z = z*rcx - y*rsx; + y=t; + z += 4; + return [96*(0.5+x/z), 96*(0.5+y/z)]; + } + + var a; + // draw a series of lines to make up our cube + h.clear(); + a = p(-1,-1,-1); h.moveTo(a[0],a[1]); + a = p(1,-1,-1); h.lineTo(a[0],a[1]); + a = p(1,1,-1); h.lineTo(a[0],a[1]); + a = p(-1,1,-1); h.lineTo(a[0],a[1]); + a = p(-1,-1,-1); h.lineTo(a[0],a[1]); + a = p(-1,-1,1); h.moveTo(a[0],a[1]); + a = p(1,-1,1); h.lineTo(a[0],a[1]); + a = p(1,1,1); h.lineTo(a[0],a[1]); + a = p(-1,1,1); h.lineTo(a[0],a[1]); + a = p(-1,-1,1); h.lineTo(a[0],a[1]); + a = p(-1,-1,-1); h.moveTo(a[0],a[1]); + a = p(-1,-1,1); h.lineTo(a[0],a[1]); + a = p(1,-1,-1); h.moveTo(a[0],a[1]); + a = p(1,-1,1); h.lineTo(a[0],a[1]); + a = p(1,1,-1); h.moveTo(a[0],a[1]); + a = p(1,1,1); h.lineTo(a[0],a[1]); + a = p(-1,1,-1); h.moveTo(a[0],a[1]); + a = p(-1,1,1); h.lineTo(a[0],a[1]); + g.drawImage({width:96,height:96,buffer:h.buffer},(240-96)/2,68); + } + + setInterval(draw,50); + }, + function() { + g.reset(); + g.setBgColor("#660099");g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",36); + g.drawString("2",200,120); + g.setFont("6x8",2); + + var x = 90, y = 30, h=21; + animate([ + ()=>g.drawString("That's it!",x,y+=h), + ()=>{g.drawString("Press",x,y+=h*3); + g.drawString("Button 2",x,y+=h); + g.drawString("to start",x,y+=h); + g.drawString("Bangle.js",x,y+=h);} + ],400); + } +]; + +var sceneNumber = 0; + +function move(dir) { + if (dir>0 && sceneNumber+1 == scenes.length) return; // at the end + sceneNumber = (sceneNumber+dir)%scenes.length; + if (sceneNumber<0) sceneNumber=0; + clearInterval(); + Bangle.setLCDMode(); + g.clear(); + scenes[sceneNumber](); + if (sceneNumber>1) { + var l = scenes.length; + for (var i=0;imove(1), BTN3, {repeat:true}); +setWatch(()=>{ + // If we're on the last page + if (sceneNumber == scenes.length-1) { + load(); + } +}, BTN2, {repeat:true,edge:"falling"}); +setWatch(()=>move(-1), BTN1, {repeat:true}); + +Bangle.setLCDTimeout(0); +Bangle.setLCDPower(1); +move(0); diff --git a/apps/mywelcome/app.png b/apps/mywelcome/app.png new file mode 100644 index 000000000..ebbf254bd Binary files /dev/null and b/apps/mywelcome/app.png differ diff --git a/apps/mywelcome/boot.js b/apps/mywelcome/boot.js new file mode 100644 index 000000000..84d235bc5 --- /dev/null +++ b/apps/mywelcome/boot.js @@ -0,0 +1,9 @@ +(function() { + let s = require('Storage').readJSON('mywelcome.json', 1) || {}; + if (!s.welcomed) { + setTimeout(() => { + require('Storage').write('mywelcome.json', {welcomed: true}) + load('mywelcome.app.js') + }) + } +})() diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html new file mode 100644 index 000000000..87713e7a2 --- /dev/null +++ b/apps/mywelcome/custom.html @@ -0,0 +1,78 @@ + + + + + + +

Line 1:

+

Line 2:

+

Line 3 (smaller):

+

Line 4 (smaller):

+

+

+

This is currently Christmas-themed, but more themes will be added in the future.

+ + + + + + diff --git a/apps/mywelcome/settings.js b/apps/mywelcome/settings.js new file mode 100644 index 000000000..cf7208d65 --- /dev/null +++ b/apps/mywelcome/settings.js @@ -0,0 +1,18 @@ +(function(back) { + let settings = require('Storage').readJSON('mywelcome.json', 1) + || require('Storage').readJSON('setting.json', 1) || {} + E.showMenu({ + '': { 'title': 'Welcome App' }, + 'Run next boot': { + value: !settings.welcomed, + format: v => v ? 'Yes' : 'No', + onchange: v => require('Storage').write('mywelcome.json', {welcomed: !v}), + }, + 'Run Now': () => load('mywelcome.app.js'), + 'Turn off & run next': () => { + require('Storage').write('mywelcome.json', {welcomed: false}); + Bangle.off(); + }, + '< Back': back, + }) +})