1
0
Fork 0

Merge branch 'master' of github.com:espruino/BangleApps

master
Johan Bové 2020-04-07 22:17:04 +02:00
commit 6d12ffe452
319 changed files with 10758 additions and 2178 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.htaccess
node_modules
package-lock.json

3
.travis.yml Normal file
View File

@ -0,0 +1,3 @@
language: node_js
node_js:
- "node"

478
Bangle.js.svg Normal file
View File

@ -0,0 +1,478 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="svg4149"
viewBox="0 0 19.191 30.044001"
height="30.044001"
width="19.191">
<defs
id="defs4151">
<linearGradient
id="linearGradient28799-4">
<stop
id="stop28801-13"
offset="0"
style="stop-color:#fefefc;stop-opacity:1;" />
<stop
style="stop-color:#fefefc;stop-opacity:1"
offset="0.75733864"
id="stop28807-8" />
<stop
id="stop28803-8"
offset="1"
style="stop-color:#d4d4d4;stop-opacity:1" />
</linearGradient>
<radialGradient
r="14.572236"
fy="137.66095"
fx="223.19559"
cy="137.66095"
cx="223.19559"
gradientTransform="matrix(1.0857794,-0.03431182,0.03943781,1.2479887,-154.54194,-26.542647)"
gradientUnits="userSpaceOnUse"
id="radialGradient29032"
xlink:href="#linearGradient28799-4" />
<radialGradient
r="14.572236"
fy="137.66095"
fx="223.19559"
cy="137.66095"
cx="223.19559"
gradientTransform="matrix(0.81524244,-0.03431182,0.02961133,1.2479887,-129.43743,-26.542647)"
gradientUnits="userSpaceOnUse"
id="radialGradient29648"
xlink:href="#linearGradient28799-4" />
<linearGradient
id="linearGradient14392-7">
<stop
style="stop-color:#3e2a06;stop-opacity:1"
offset="0"
id="stop14394-8" />
<stop
style="stop-color:#ad780a;stop-opacity:1"
offset="1"
id="stop14396-6" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient14392-7"
id="linearGradient14477"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.87491199,0,0,0.81148755,60.367186,62.586953)"
x1="442.03912"
y1="371.54401"
x2="490.12241"
y2="293.58548" />
<linearGradient
xlink:href="#linearGradient14392-7"
id="linearGradient14485"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(4e-6,9.6185359e-6)"
x1="442.03912"
y1="371.54401"
x2="490.12241"
y2="293.58548" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14186-9">
<path
id="path14188-1"
d="m 137.57703,281.0191 c 1.59929,-0.66295 3.3982,-0.78361 5.10074,-0.46963 1.70253,0.31398 3.31141,1.04948 4.74342,2.02239 2.86402,1.94583 4.98821,4.77774 7.02263,7.57952 4.67189,6.43406 9.16868,13.00227 13.24488,19.8293 3.30635,5.53766 6.34352,11.25685 10.16415,16.45304 2.49398,3.3919 5.3066,6.53947 7.813,9.92221 2.50639,3.38273 4.72794,7.05586 5.83931,11.11662 1.44411,5.27653 0.88463,11.09291 -1.62666,15.95302 -1.76663,3.41896 -4.47646,6.35228 -7.77242,8.33898 -3.29595,1.9867 -7.17064,3.01444 -11.01635,2.87021 -6.11413,-0.2293 -11.69944,-3.28515 -17.38362,-5.54906 -11.58097,-4.6125 -24.15978,-6.0594 -36.09666,-9.65174 -3.66859,-1.10404 -7.27582,-2.4107 -10.96988,-3.42629 -1.64125,-0.45122 -3.30866,-0.8482 -4.85875,-1.55144 -1.55008,-0.70325 -2.999548,-1.7491 -3.86171,-3.21675 -0.666391,-1.13439 -0.948386,-2.47002 -0.930187,-3.78554 0.0182,-1.31552 0.325889,-2.61453 0.773815,-3.85158 0.895851,-2.47409 2.343262,-4.71374 3.320162,-7.15696 1.59511,-3.98935 1.88169,-8.38839 1.66657,-12.67942 -0.21511,-4.29103 -0.91078,-8.54478 -1.20454,-12.83115 -0.13118,-1.91406 -0.18066,-3.85256 0.18479,-5.73598 0.36545,-1.88343 1.17577,-3.72459 2.55771,-5.05541 1.27406,-1.22693 2.96492,-1.95531 4.69643,-2.31651 1.73151,-0.3612 3.51533,-0.37747 5.28367,-0.33762 1.76833,0.0399 3.54067,0.13425 5.30351,-0.0106 1.76284,-0.14488 3.53347,-0.54055 5.06911,-1.41828 1.45996,-0.83447 2.65433,-2.0745 3.64374,-3.43424 0.9894,-1.35974 1.78909,-2.84573 2.60891,-4.31396 0.81983,-1.46823 1.66834,-2.93151 2.74157,-4.22611 1.07324,-1.2946 2.38923,-2.42304 3.94266,-3.06698"
style="fill:#402c07;fill-opacity:1;stroke:none" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath15031-3">
<path
style="fill:none;stroke:#729fcf;stroke-width:0.125;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 248.06066,184.61218 c -2.60091,3.4397 -5.23338,6.85555 -7.89695,10.24696 -2.83301,3.60715 -5.70116,7.1866 -8.58729,10.75139 -3.54135,4.37407 -7.15155,8.79286 -9.44912,13.93045 -1.97407,4.41422 -2.91014,9.20748 -4.26498,13.84932 -1.53862,5.27153 -3.62627,10.37023 -5.97071,15.33612 -2.1648,4.58539 -4.54978,9.06293 -6.93891,13.53553 -1.73823,3.25407 -3.50515,6.58104 -4.10782,10.22071 -0.47627,2.87632 -0.19849,5.84423 0.53376,8.66626 0.73224,2.82202 1.90964,5.5106 3.23775,8.10601 5.66724,11.07504 14.17003,20.62168 24.24176,27.92472 4.57063,3.31418 9.46669,6.18109 14.60245,8.52595 2.78247,1.27041 5.71355,2.40436 8.77186,2.45744 1.52915,0.0265 3.0741,-0.22544 4.47434,-0.84055 1.40024,-0.6151 2.65068,-1.60373 3.48254,-2.88709 1.02278,-1.5779 1.36992,-3.53829 1.16461,-5.40743 -0.2053,-1.86914 -0.93484,-3.65294 -1.91324,-5.25873 -2.38997,-3.92251 -6.1652,-6.76055 -9.79642,-9.57343 -7.84055,-6.07358 -15.42466,-12.48038 -22.68212,-19.23996 -2.04911,-1.90854 -4.0984,-3.87759 -5.53019,-6.28412 -1.3943,-2.34352 -2.14763,-5.01375 -2.65783,-7.69253 -1.39963,-7.34872 -1.0404,-15.0827 1.45958,-22.13343 0.97802,-2.75834 2.27066,-5.39225 3.51815,-8.03965 2.1622,-4.58855 4.21133,-9.26299 7.04933,-13.46723 3.53268,-5.23334 8.24484,-9.67275 11.15147,-15.27803 2.45457,-4.7335 3.49353,-10.05742 4.36185,-15.31831 0.66531,-4.03089 1.24751,-8.07549 1.74613,-12.13037"
id="path15033-2" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14822-9">
<path
d="m 386.1875,285.32775 c -0.40516,-1.10369 -1.11845,-2.08156 -1.9907,-2.86987 -0.87226,-0.78832 -1.90049,-1.39229 -2.98278,-1.85155 -2.16459,-0.91852 -4.52053,-1.26149 -6.83152,-1.69556 -2.17919,-0.40931 -4.34179,-0.90631 -6.52782,-1.27734 -2.27136,-0.38551 -4.6179,-0.63213 -6.8653,-0.1253 -1.96583,0.44333 -3.7845,1.45879 -5.27172,2.81864 -1.48723,1.35984 -2.64911,3.0564 -3.48499,4.89007 -1.47218,3.22952 -1.93451,6.86503 -1.65394,10.40316 0.20882,2.63325 0.87532,5.34594 2.60877,7.33912 1.40065,1.61052 3.38733,2.61526 5.43398,3.22092 3.52502,1.04316 7.36663,0.98822 10.86038,-0.1553 5.76689,-1.93113 10.87568,-5.77387 14.33034,-10.77903 1.13861,-1.64963 2.11217,-3.44809 2.5532,-5.4034 0.33597,-1.48955 0.34831,-3.08112 -0.1779,-4.51456"
id="path14824-1"
style="fill:none;stroke:#ce5c00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14822-2-4">
<path
d="m 386.1875,285.32775 c -0.40516,-1.10369 -1.11845,-2.08156 -1.9907,-2.86987 -0.87226,-0.78832 -1.90049,-1.39229 -2.98278,-1.85155 -2.16459,-0.91852 -4.52053,-1.26149 -6.83152,-1.69556 -2.17919,-0.40931 -4.34179,-0.90631 -6.52782,-1.27734 -2.27136,-0.38551 -4.6179,-0.63213 -6.8653,-0.1253 -1.96583,0.44333 -3.7845,1.45879 -5.27172,2.81864 -1.48723,1.35984 -2.64911,3.0564 -3.48499,4.89007 -1.47218,3.22952 -1.93451,6.86503 -1.65394,10.40316 0.20882,2.63325 0.87532,5.34594 2.60877,7.33912 1.40065,1.61052 3.38733,2.61526 5.43398,3.22092 3.52502,1.04316 7.36663,0.98822 10.86038,-0.1553 5.76689,-1.93113 10.87568,-5.77387 14.33034,-10.77903 1.13861,-1.64963 2.11217,-3.44809 2.5532,-5.4034 0.33597,-1.48955 0.34831,-3.08112 -0.1779,-4.51456"
id="path14824-6-5"
style="fill:none;stroke:#ce5c00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</clipPath>
<linearGradient
id="linearGradient14518-6">
<stop
id="stop14540-0"
offset="0"
style="stop-color:#110800;stop-opacity:1;" />
<stop
style="stop-color:#a65a00;stop-opacity:0.80000001;"
offset="0.59066743"
id="stop14542-5" />
<stop
style="stop-color:#ff921e;stop-opacity:0;"
offset="1"
id="stop14522-4" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14489-3">
<path
style="fill:url(#linearGradient14485);fill-opacity:1;stroke:none"
d="m 516.00562,331.8756 c -0.6714,1.71905 -1.62682,3.32679 -2.81579,4.73826 -2.6238,3.11482 -6.268,5.17039 -9.89648,7.01985 -6.1886,3.15437 -12.60169,5.92177 -18.41964,9.71654 -3.89802,2.54249 -7.4959,5.52671 -10.86016,8.74238 -2.87719,2.75012 -5.60582,5.68745 -8.83247,8.01771 -3.25567,2.35122 -7.01915,4.05426 -10.99061,4.6502 -4.83026,0.72481 -9.82134,-0.21289 -14.29898,-2.16416 -3.13754,-1.36728 -6.15569,-3.3229 -7.96301,-6.22931 -1.81425,-2.91754 -2.22807,-6.48813 -2.23266,-9.92375 -0.008,-6.07666 1.11824,-12.09004 2.17848,-18.07349 0.88097,-4.97177 1.71949,-9.95483 2.26013,-14.97502 0.98337,-9.13118 0.9763,-18.35278 0.3199,-27.51327 -0.10993,-1.53416 -0.23754,-3.0832 -0.008,-4.60412 0.22922,-1.52092 0.85475,-3.0367 2.02069,-4.03986 1.07696,-0.9266 2.52093,-1.33598 3.93947,-1.4145 1.41854,-0.0785 2.83404,0.14655 4.23982,0.35197 3.31254,0.48405 6.65159,0.8649 9.88917,1.71656 2.04284,0.53738 4.03315,1.25925 6.0722,1.81081 3.40258,0.92039 6.96639,1.36144 10.46739,0.95192 3.76917,-0.44089 7.42987,-1.85678 11.22363,-1.76474 1.55658,0.0378 3.1015,0.33171 4.58649,0.79985 1.51539,0.47772 3.00914,1.16182 4.12281,2.29512 0.84639,0.8613 1.43579,1.94539 1.87872,3.06879 0.65982,1.67352 1.01492,3.457 1.16703,5.24945 0.13475,1.58788 0.11343,3.19441 0.41433,4.75933 0.49503,2.57458 1.84746,4.92305 3.52848,6.93494 1.68102,2.01189 3.68982,3.72048 5.69641,5.40783 1.99908,1.68103 4.0106,3.35469 6.16708,4.82839 1.0121,0.69165 2.05642,1.33949 3.01736,2.10062 0.96094,0.76113 1.84466,1.6468 2.44543,2.71535 0.81492,1.44944 1.06377,3.2077 0.68307,4.82635 h 2e-5"
id="path14491-6" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14481-1">
<path
style="fill:url(#linearGradient14485);fill-opacity:1;stroke:none"
d="m 516.00562,331.8756 c -0.6714,1.71905 -1.62682,3.32679 -2.81579,4.73826 -2.6238,3.11482 -6.268,5.17039 -9.89648,7.01985 -6.1886,3.15437 -12.60169,5.92177 -18.41964,9.71654 -3.89802,2.54249 -7.4959,5.52671 -10.86016,8.74238 -2.87719,2.75012 -5.60582,5.68745 -8.83247,8.01771 -3.25567,2.35122 -7.01915,4.05426 -10.99061,4.6502 -4.83026,0.72481 -9.82134,-0.21289 -14.29898,-2.16416 -3.13754,-1.36728 -6.15569,-3.3229 -7.96301,-6.22931 -1.81425,-2.91754 -2.22807,-6.48813 -2.23266,-9.92375 -0.008,-6.07666 1.11824,-12.09004 2.17848,-18.07349 0.88097,-4.97177 1.71949,-9.95483 2.26013,-14.97502 0.98337,-9.13118 0.9763,-18.35278 0.3199,-27.51327 -0.10993,-1.53416 -0.23754,-3.0832 -0.008,-4.60412 0.22922,-1.52092 0.85475,-3.0367 2.02069,-4.03986 1.07696,-0.9266 2.52093,-1.33598 3.93947,-1.4145 1.41854,-0.0785 2.83404,0.14655 4.23982,0.35197 3.31254,0.48405 6.65159,0.8649 9.88917,1.71656 2.04284,0.53738 4.03315,1.25925 6.0722,1.81081 3.40258,0.92039 6.96639,1.36144 10.46739,0.95192 3.76917,-0.44089 7.42987,-1.85678 11.22363,-1.76474 1.55658,0.0378 3.1015,0.33171 4.58649,0.79985 1.51539,0.47772 3.00914,1.16182 4.12281,2.29512 0.84639,0.8613 1.43579,1.94539 1.87872,3.06879 0.65982,1.67352 1.01492,3.457 1.16703,5.24945 0.13475,1.58788 0.11343,3.19441 0.41433,4.75933 0.49503,2.57458 1.84746,4.92305 3.52848,6.93494 1.68102,2.01189 3.68982,3.72048 5.69641,5.40783 1.99908,1.68103 4.0106,3.35469 6.16708,4.82839 1.0121,0.69165 2.05642,1.33949 3.01736,2.10062 0.96094,0.76113 1.84466,1.6468 2.44543,2.71535 0.81492,1.44944 1.06377,3.2077 0.68307,4.82635 h 2e-5"
id="path14483-3" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14473-5">
<path
style="fill:url(#linearGradient14477);fill-opacity:1;stroke:none"
d="m 511.82669,331.89986 c -0.60617,1.40358 -1.44178,2.70776 -2.46357,3.84504 -2.32524,2.58805 -5.50706,4.22246 -8.65855,5.69652 -5.42045,2.53534 -11.00071,4.7788 -16.11556,7.88485 -3.38301,2.05437 -6.53696,4.47221 -9.50169,7.09434 -2.52461,2.23286 -4.92977,4.6271 -7.72763,6.50627 -2.88313,1.93644 -6.17534,3.29888 -9.61582,3.77358 -4.21817,0.58201 -8.55335,-0.18335 -12.51035,-1.75619 -2.7091,-1.07682 -5.36625,-2.6185 -6.96693,-5.05501 -1.53813,-2.3413 -1.9333,-5.25173 -1.95338,-8.053 -0.0354,-4.93859 0.96501,-9.81816 1.90598,-14.66641 0.78215,-4.02995 1.52682,-8.0717 1.97741,-12.15204 0.81784,-7.40607 0.66114,-14.88536 0.27989,-22.32668 -0.064,-1.24906 -0.13361,-2.5105 0.0789,-3.74301 0.21256,-1.23251 0.73646,-2.45247 1.68171,-3.27147 0.93914,-0.8137 2.20952,-1.15942 3.45104,-1.2115 1.24152,-0.0521 2.47642,0.16381 3.70512,0.34927 2.88969,0.43617 5.81087,0.7091 8.65215,1.39297 1.78651,0.43 3.53091,1.02011 5.31265,1.46945 2.98632,0.75312 6.09645,1.10709 9.15804,0.77247 3.2941,-0.36004 6.50688,-1.50957 9.81969,-1.43207 1.35838,0.0318 2.70755,0.27142 4.01277,0.64907 1.31265,0.3798 2.61744,0.9202 3.6071,1.86246 0.72535,0.69061 1.25006,1.56936 1.64371,2.49029 0.57625,1.34814 0.88626,2.79994 1.02105,4.25986 0.11897,1.28855 0.10468,2.59405 0.36251,3.86214 0.4369,2.14878 1.63966,4.08139 3.11506,5.70353 1.4754,1.62214 3.22406,2.96752 4.9559,4.31247 1.75634,1.36398 3.50975,2.7398 5.39565,3.91818 0.88228,0.55128 1.79349,1.05932 2.63473,1.67142 0.84123,0.61209 1.61878,1.33907 2.14473,2.23668 0.68386,1.16711 0.90229,2.59857 0.59763,3.91652 h 2e-5"
id="path14475-9" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath14656-7">
<path
d="m 304.84727,225.44951 c 6.01627,4.92338 9.77707,12.317 10.94319,20.00305 1.8254,12.03148 -2.34636,24.07488 -6.44591,35.53273 -0.81749,2.28481 -1.63978,4.59033 -1.96473,6.99513 -0.32495,2.4048 -0.11897,4.94825 1.02948,7.08594 1.30279,2.42497 3.73227,4.12469 6.36677,4.92292 2.58877,0.78438 5.39365,0.76103 8.01466,0.0922 2.62101,-0.66882 5.06097,-1.96774 7.18716,-3.63996 5.48002,-4.30997 8.75799,-10.98552 10.01043,-17.84394 1.25243,-6.85841 0.63908,-13.92879 -0.551,-20.7983 -1.62313,-9.36916 -4.30898,-18.53961 -7.74003,-27.40773 -2.51766,-6.50732 -5.46794,-12.91081 -9.61753,-18.52016 -4.04611,-5.46946 -9.16208,-10.08954 -13.10336,-15.63502 -2.73209,-3.84412 -4.89983,-8.13097 -8.1917,-11.50812 -1.64594,-1.68858 -3.57654,-3.13527 -5.76975,-4.00143 -2.19321,-0.86616 -4.66356,-1.12446 -6.93254,-0.48249 -1.50468,0.42573 -2.89193,1.23739 -4.03791,2.30136 -1.14598,1.06397 -2.05268,2.37637 -2.69275,3.80312 -1.28015,2.85349 -1.47719,6.11241 -0.96512,9.1977 0.66114,3.98343 2.44407,7.69333 4.46569,11.18873 2.27396,3.93169 4.918,7.71926 8.38442,10.65407 3.5985,3.04664 7.96161,5.07413 11.61053,8.0602"
id="path14658-9"
style="fill:none;stroke:#c17d11;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</clipPath>
<clipPath
id="clipPath29644-8"
clipPathUnits="userSpaceOnUse">
<path
id="path29646-8"
d="m 54.232441,122.36218 c -1.780966,0.097 -3.484616,0.91899 -4.787851,2.1367 -1.303235,1.21771 -2.221372,2.81176 -2.786181,4.50357 -1.129618,3.38363 -0.875481,7.05177 -0.618698,10.60973 0.23251,3.22162 0.470404,6.50533 1.676785,9.50158 0.60319,1.49813 1.450247,2.91021 2.580338,4.06395 1.130092,1.15374 2.551736,2.04189 4.118297,2.43447 1.468838,0.36809 3.038161,0.29183 4.482784,-0.16209 1.444623,-0.45391 2.763917,-1.27887 3.846236,-2.33791 1.57904,-1.54507 2.643262,-3.5662 3.253449,-5.68947 0.610186,-2.12328 0.784157,-4.35155 0.752401,-6.56053 -0.03974,-2.76435 -0.400909,-5.53851 -1.265755,-8.16439 -0.864846,-2.62588 -2.245742,-5.10327 -4.172795,-7.08561 -0.933308,-0.96009 -1.997766,-1.80513 -3.198586,-2.39747 -1.200819,-0.59233 -2.54344,-0.92535 -3.880424,-0.85253"
style="fill:url(#radialGradient29648);fill-opacity:1;stroke:none" />
</clipPath>
<linearGradient
id="linearGradient28469-0">
<stop
style="stop-color:#d2940a;stop-opacity:1"
offset="0"
id="stop28479-4" />
<stop
style="stop-color:#d89c08;stop-opacity:1"
offset="0.75143719"
id="stop28477-2" />
<stop
id="stop28485-7"
offset="0.86579126"
style="stop-color:#b67e07;stop-opacity:1;" />
<stop
id="stop28473-1"
offset="1"
style="stop-color:#946106;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient28275-8">
<stop
id="stop28277-1"
offset="0"
style="stop-color:#ad780a;stop-opacity:1" />
<stop
style="stop-color:#d89e08;stop-opacity:1"
offset="0.11972899"
id="stop28291-9" />
<stop
style="stop-color:#edb80b;stop-opacity:1"
offset="0.25514477"
id="stop28289-6" />
<stop
style="stop-color:#ebc80d;stop-opacity:1"
offset="0.39194193"
id="stop28287-6" />
<stop
style="stop-color:#f5d838;stop-opacity:1"
offset="0.52741116"
id="stop28285-7" />
<stop
style="stop-color:#f6d811;stop-opacity:1"
offset="0.76906693"
id="stop28283-7" />
<stop
id="stop28279-5"
offset="1"
style="stop-color:#f5cd31;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient29529-6">
<stop
id="stop29531-0"
offset="0"
style="stop-color:#3a2903;stop-opacity:1;" />
<stop
style="stop-color:#735208;stop-opacity:1"
offset="0.55472803"
id="stop29539-7" />
<stop
id="stop29533-8"
offset="1"
style="stop-color:#ac8c04;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient29477-3">
<stop
id="stop29479-5"
offset="0"
style="stop-color:#757574;stop-opacity:0" />
<stop
style="stop-color:#757574;stop-opacity:1;"
offset="0.26291031"
id="stop29487-5" />
<stop
style="stop-color:#757574;stop-opacity:1;"
offset="0.5"
id="stop29485-0" />
<stop
id="stop29481-3"
offset="1"
style="stop-color:#757574;stop-opacity:0" />
</linearGradient>
<linearGradient
id="linearGradient29336-6">
<stop
id="stop29338-1"
offset="0"
style="stop-color:#646464;stop-opacity:0" />
<stop
style="stop-color:#646464;stop-opacity:0.5825243"
offset="0.30628255"
id="stop29344-7" />
<stop
id="stop29354-5"
offset="0.47000006"
style="stop-color:#646464;stop-opacity:1" />
<stop
style="stop-color:#646464;stop-opacity:0.25728154"
offset="0.72834015"
id="stop29356-3" />
<stop
id="stop29340-6"
offset="1"
style="stop-color:#646464;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient28976-3">
<stop
id="stop28978-7"
offset="0"
style="stop-color:#747474;stop-opacity:1" />
<stop
style="stop-color:#8c8c8c;stop-opacity:1;"
offset="0.125"
id="stop29259-5" />
<stop
style="stop-color:#a4a4a4;stop-opacity:1;"
offset="0.25"
id="stop29257-0" />
<stop
style="stop-color:#d4d4d4;stop-opacity:1"
offset="0.5"
id="stop28984-4" />
<stop
id="stop28986-0"
offset="0.61919296"
style="stop-color:#d4d4d4;stop-opacity:1" />
<stop
id="stop28980-2"
offset="1"
style="stop-color:#7c7c7c;stop-opacity:1" />
</linearGradient>
<clipPath
id="clipPath29028-5"
clipPathUnits="userSpaceOnUse">
<path
id="path29030-7"
d="m 85.75,122.36218 c -2.780425,1.91023 -5.110569,4.57487 -6.25,7.75 -1.436029,4.00163 -0.885838,8.48071 0.5,12.5 1.419488,4.11688 3.793788,8.04098 7.37932,10.51234 1.792766,1.23567 3.868091,2.08301 6.030402,2.33859 2.162311,0.25558 4.409274,-0.0949 6.340278,-1.10093 2.35312,-1.22596 4.14782,-3.37278 5.26217,-5.78076 1.11436,-2.40798 1.5888,-5.0701 1.73783,-7.71924 0.18989,-3.37546 -0.14047,-6.80646 -1.25,-10 -1.20527,-3.46909 -3.39005,-6.67055 -6.472754,-8.6666 -1.541351,-0.99803 -3.291947,-1.68356 -5.110883,-1.93515 -1.818936,-0.25158 -3.704766,-0.0633 -5.416363,0.60175 -0.975471,0.37901 -1.887438,0.9074 -2.75,1.5"
style="fill:url(#radialGradient29032);fill-opacity:1;stroke:none" />
</clipPath>
<linearGradient
id="linearGradient28935-8">
<stop
id="stop28937-7"
offset="0"
style="stop-color:#949494;stop-opacity:0.39215687;" />
<stop
style="stop-color:#949494;stop-opacity:1;"
offset="0.5"
id="stop28943-3" />
<stop
id="stop28939-8"
offset="1"
style="stop-color:#949494;stop-opacity:0.39215687;" />
</linearGradient>
<linearGradient
id="linearGradient28853-5">
<stop
id="stop28855-3"
offset="0"
style="stop-color:#020204;stop-opacity:1" />
<stop
style="stop-color:#020204;stop-opacity:1"
offset="0.73448181"
id="stop28865-0" />
<stop
id="stop28857-9"
offset="1"
style="stop-color:#5c5c5c;stop-opacity:1;" />
</linearGradient>
<clipPath
id="clipPath20"
clipPathUnits="userSpaceOnUse">
<path
id="path18"
d="M 0,0 H 1920 V 1080 H 0 Z" />
</clipPath>
<clipPath
id="clipPath70"
clipPathUnits="userSpaceOnUse">
<path
id="path68"
d="M 0,0 H 1920 V 1080 H 0 Z" />
</clipPath>
<clipPath
id="clipPath84"
clipPathUnits="userSpaceOnUse">
<path
id="path82"
d="M 0,0 H 1920 V 1080 H 0 Z" />
</clipPath>
</defs>
<g
transform="translate(0,-1022.3182)"
id="layer1">
<path
id="path5206"
d=""
style="fill:#000000" />
<path
id="path5204"
d=""
style="fill:#000000" />
<g
style="stroke-width:9.38626099"
id="g1740"
transform="matrix(0.1420516,0,0,-0.1420516,103.61175,1146.9897)">
<g
transform="translate(-1082.1477,-40.602139)"
style="stroke-width:9.38626099"
id="g14">
<g
style="stroke-width:9.38626099"
id="g16"
clip-path="url(#clipPath20)">
<g
style="stroke-width:9.38626099"
id="g22"
transform="translate(485.7208,801.7501)">
<path
d="M 0,0 H -6.408 V 21.503 H 0 c 1.176,0 2.129,-0.953 2.129,-2.129 V 2.129 C 2.129,0.953 1.176,0 0,0"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path24" />
</g>
<g
style="stroke-width:9.38626099"
id="g26"
transform="translate(467.9097,852.7021)">
<path
d="m 0,0 -1.811,-1.811 -10.287,10.287 1.811,1.811 c 1.424,1.425 3.734,1.425 5.159,0 L 0,5.159 C 1.425,3.734 1.425,1.425 0,0"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path28" />
</g>
<g
style="stroke-width:9.38626099"
id="g30"
transform="translate(457.6224,762.014)">
<path
d="m 0,0 -1.811,1.811 10.287,10.287 1.811,-1.811 c 1.425,-1.425 1.425,-3.734 0,-5.159 L 5.159,0 C 3.734,-1.425 1.425,-1.425 0,0"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path32" />
</g>
<g
style="stroke-width:9.38626099"
id="g34"
transform="translate(449.515,753.7302)">
<path
d="m 0,0 v 4.934 c 0,0 -24.07,-8.855 -32.093,-8.855 -8.023,0 -32.093,8.855 -32.093,8.855 V 0 c 0,-2.713 0.889,-5.101 2.238,-6.509 1.433,-7.677 3.125,-26.35 3.681,-32.625 0,-4.333 2.261,-7.845 5.052,-7.845 h 42.245 c 2.79,0 5.051,3.512 5.051,7.845 v 10e-4 c 0.556,6.275 2.248,24.947 3.682,32.624 C -0.889,-5.101 0,-2.714 0,0"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path36" />
</g>
<g
style="stroke-width:9.38626099"
id="g38"
transform="translate(385.3291,871.2729)">
<path
d="m 0,0 v -4.934 c 0,0 24.07,8.855 32.093,8.855 8.023,0 32.093,-8.855 32.093,-8.855 V 0 c 0,2.713 -0.889,5.101 -2.238,6.509 -1.433,7.677 -3.125,26.35 -3.681,32.624 0,4.333 -2.261,7.846 -5.051,7.846 H 10.97 c -2.79,0 -5.051,-3.513 -5.051,-7.846 v 0 C 5.363,32.858 3.671,14.186 2.237,6.509 0.889,5.101 0,2.714 0,0"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path40" />
</g>
<g
style="stroke-width:9.38626099"
id="g42"
transform="translate(371.6938,858.2298)">
<path
d="m 0,0 c -25.255,-25.255 -25.255,-66.201 0,-91.456 25.255,-25.255 66.201,-25.255 91.456,0 25.256,25.255 25.255,66.201 0,91.456 C 66.201,25.255 25.255,25.255 0,0 m 85.935,-85.935 c -22.206,-22.205 -58.208,-22.205 -80.413,0 -22.206,22.206 -22.206,58.208 -0.001,80.414 22.206,22.205 58.208,22.205 80.414,0 22.206,-22.206 22.206,-58.208 0,-80.414"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path44" />
</g>
</g>
</g>
<path
d="m -665.93071,765.84586 h 2.41 v -34.464 h -2.41 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path76" />
<g
transform="translate(-1082.1477,-40.602139)"
style="stroke-width:9.38626099"
id="g78">
<g
style="stroke-width:9.38626099"
id="g80"
clip-path="url(#clipPath84)">
<g
style="stroke-width:9.38626099"
id="g86"
transform="translate(438.6725,783.7013)">
<path
d="m 0,0 c -1.568,-1.333 -3.448,-1.998 -5.643,-1.998 -3.761,0 -6.583,1.704 -8.464,5.113 l 2.058,1.235 c 0.666,-1.215 1.469,-2.156 2.409,-2.822 0.98,-0.744 2.312,-1.117 3.997,-1.117 3.683,0 5.525,1.567 5.525,4.703 0,1.371 -0.646,2.566 -1.939,3.585 -0.393,0.235 -0.951,0.5 -1.675,0.793 -0.726,0.294 -1.617,0.637 -2.675,1.029 -1.96,0.705 -3.37,1.411 -4.231,2.116 -1.255,1.019 -1.881,2.488 -1.881,4.408 0,1.646 0.626,3.057 1.881,4.232 1.214,1.214 2.8,1.822 4.76,1.822 2.704,0 4.996,-0.94 6.877,-2.821 L -0.764,18.75 c -1.332,1.332 -3.037,1.998 -5.114,1.998 -2.821,0 -4.232,-1.234 -4.232,-3.703 0,-1.097 0.431,-1.998 1.294,-2.703 0.705,-0.55 1.821,-1.079 3.35,-1.587 1.331,-0.471 2.429,-0.911 3.291,-1.323 0.862,-0.411 1.508,-0.774 1.94,-1.087 C 1.449,9.052 2.292,7.308 2.292,5.114 2.292,2.998 1.528,1.293 0,0"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path88" />
</g>
</g>
</g>
<path
id="path264"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
d="m -662.67221,771.89946 c 0,-1.134 -0.919,-2.053 -2.053,-2.053 -1.134,0 -2.054,0.919 -2.054,2.053 0,1.134 0.92,2.053 2.054,2.053 1.134,0 2.053,-0.919 2.053,-2.053" />
<g
style="stroke-width:9.38626099"
id="g266"
transform="translate(-662.03281,778.14876)">
<path
d="m 0,0 2.015,-1.321 21.656,33.028 -2.015,1.322 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
id="path268" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

7
CHANGELOG.md Normal file
View File

@ -0,0 +1,7 @@
App Loader ChangeLog
====================
Changed for individual apps are listed in `apps/appname/ChangeLog`
* `Remove All Apps` now doesn't perform a reset before erase - fixes inability to update firmware if settings are wrong
* Added optional `README.md` file for apps

270
README.md
View File

@ -1,7 +1,14 @@
Bangle.js App Loader (and Apps) Bangle.js App Loader (and Apps)
================================ ================================
Try it live at [banglejs.com/apps](https://banglejs.com/apps) [![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps)
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
submitting code to this repository you confirm that you are happy with it being MIT licensed,
and that it is not licensed in another way that would make this impossible.
## How does it work? ## How does it work?
@ -12,15 +19,26 @@ it with the files it sees in the watch's storage.
* To upload an app, BangleAppLoader checks the files that are * To upload an app, BangleAppLoader checks the files that are
listed in `apps.json`, loads them, and sends them over Web Bluetooth. listed in `apps.json`, loads them, and sends them over Web Bluetooth.
## Getting Started
Check out:
* [Building your first Bangle.js Application](https://www.espruino.com/Bangle.js+First+App)
* [Adding an app to the Bangle.js App Loader](https://www.espruino.com/Bangle.js+App+Loader)
* [Customising the App Loader](https://www.espruino.com/Bangle.js+App+Loader+Custom)
## What filenames are used ## What filenames are used
Filenames in storage are limited to 8 characters. To Filenames in storage are limited to 8 characters. To
easily distinguish between file types, we use the following: easily distinguish between file types, we use the following:
* `+stuff` is JSON for an app * `stuff.info` is JSON that describes an app - this is auto-generated by the App Loader
* `*stuff` is an image * `stuff.img` is an image
* `-stuff` is JS code * `stuff.app.js` is JS code for applications
* `=stuff` is JS code for stuff that is run at boot time - eg. handling settings or creating widgets on the clock screen * `stuff.wid.js` is JS code for widgets
* `stuff.settings.js` is JS code for the settings menu
* `stuff.boot.js` is JS code that automatically gets run at boot time
* `stuff.json` is used for JSON settings for an app
## Developing your own app ## Developing your own app
@ -31,35 +49,25 @@ easily distinguish between file types, we use the following:
## Adding your app to the menu ## Adding your app to the menu
* Come up with a unique 7 character name, we'll assume `7chname` * Come up with a unique (all lowercase, nu spaces) name, we'll assume `7chname`. Bangle.js
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
try and keep filenames short to avoid overflowing the buffer.
* Create a folder called `apps/<id>`, lets assume `apps/7chname` * Create a folder called `apps/<id>`, lets assume `apps/7chname`
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or... * We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
* `apps/7chname/app.png` should be a 48px icon * `apps/7chname/app.png` should be a 48px icon
* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String" * Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
* Create an entry in `apps/7chname/app.json` as follows:
```
{
"name":"Short Name",
"icon":"*7chname",
"src":"-7chname"
}
```
See `app.json / widget.json` below for more info on the correct format.
* Create an entry in `apps.json` as follows: * Create an entry in `apps.json` as follows:
``` ```
{ "id": "7chname", { "id": "7chname",
"name": "My app's human readable name", "name": "My app's human readable name",
"shortName" : "Short Name",
"icon": "app.png", "icon": "app.png",
"description": "A detailed description of my great app", "description": "A detailed description of my great app",
"tags": "", "tags": "",
"storage": [ "storage": [
{"name":"+7chname","url":"app.json"}, {"name":"7chname.app.js","url":"app.js"},
{"name":"-7chname","url":"app.js"}, {"name":"7chname.img","url":"app-icon.js","evaluate":true}
{"name":"*7chname","url":"app-icon.js","evaluate":true}
], ],
}, },
``` ```
@ -76,36 +84,34 @@ This is the best way to test...
* Run your personal `Bangle App Loader` at https://\<your-github-username\>.github.io/BangleApps/index.html to load apps onto your device * Run your personal `Bangle App Loader` at https://\<your-github-username\>.github.io/BangleApps/index.html to load apps onto your device
* Your apps should be inside it - if there are problems, check your web browser's 'developer console' for errors * Your apps should be inside it - if there are problems, check your web browser's 'developer console' for errors
**Note:** It's a great idea to get a local copy of the repository on your PC,
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
that there might be.
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect. Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
### Offline ### Offline
You can add the following to the Espruino Web IDE: Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/)
(4 discs), upload your files into the places described in your JSON:
``` * `app-icon.js` -> `7chname.img`
// replace with your 7chname app name
var appname = "mygreat";
require("Storage").write('*'+appname, Now load `app.js` up in the editor, and click the down-arrow to the bottom
// place app-icon.js contents here right of the `Send to Espruino` icon. Click `Storage` and then either choose
); `7chname.app.js` (if you'd uploaded your app previously), or `New File`
and then enter `7chname.app.js` as the name.
// Now, clicking the `Send to Espruino` icon will load the app directly into
require("Storage").write("+"+appname,{ Espruino **and** will automatically run it.
"name":"My Great App","type":"",
"icon":"*"+appname,
"src":"-"+appname,
});
require("Storage").write("-"+appname,` When you upload code this way, your app will even be uploaded to Bangle.js's menu
// place contents of app.js here
// be aware of double-quoting templated strings
`
```
When you upload code this way, your app will be uploaded to Bangle.js's menu
without you having to use the `Bangle App Loader` without you having to use the `Bangle App Loader`
**Note:** Widgets need to be run inside a clock or app, so if you're
developing a widget you need to go go `Settings` -> `Communications` -> `Load after saving`
and set it to `Load default application`.
## Example Applications ## Example Applications
To make the process easier we've come up with some example applications that you can use as a base To make the process easier we've come up with some example applications that you can use as a base
@ -113,21 +119,23 @@ when creating your own. Just come up with a unique 7 character name, copy `apps/
or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to
`apps.json`. `apps.json`.
**If you're making a widget** please start the name with `wid` to make
it easy to find!
### App Example ### App Example
The app example is available in [`apps/_example_app`](apps/_example_app) The app example is available in [`apps/_example_app`](apps/_example_app)
Apps are listed in the Bangle.js menu, accessible from a clock app via the middle button. Apps are listed in the Bangle.js menu, accessible from a clock app via the middle button.
* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader * `add_to_apps.json` - insert into `apps.json`, describes the app to bootloader and loader
* `app.png` - app icon - 48x48px * `app.png` - app icon - 48x48px
* `app-icon.js` - JS version of the icon (made with http://www.espruino.com/Image+Converter) for use in Bangle.js's menu * `app-icon.js` - JS version of the icon (made with http://www.espruino.com/Image+Converter) for use in Bangle.js's menu
* `app.json` - short app name for Bangle.js menu and storage filenames
* `app.js` - app code * `app.js` - app code
#### `app-icon.js` #### `app-icon.js`
The icon image and short description is used in the menu entry as selection posibility. The icon image and short description is used in Bangle.js's launcher.
Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and upload your `app.png` file. Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and upload your `app.png` file.
@ -143,24 +151,43 @@ Follow this steps to create a readable icon as image string.
Replace this line with the image converter output: Replace this line with the image converter output:
``` ```
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA==")); require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="))
``` ```
Keep in mind to use this converter for creating images you like to draw with `g.drawImage()` with your app. You can also use this converter for creating images you like to draw with `g.drawImage()` with your app.
Apps that need widgets can call `Bangle.loadWidgets()` **once** at startup to load
them, and then `Bangle.drawWidgets()` to draw them onto the screen whenever the app
has call to completely clear the screen. Widgets themselves will update as and when needed.
### Widget Example ### Widget Example
The widget example is available in [`apps/_example_widget`](apps/_example_widget) The widget example is available in [`apps/_example_widget`](apps/_example_widget)
* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader * `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader
* `widget.json` - short widget name and storage names
* `widget.js` - widget code * `widget.js` - widget code
### `app.json` / `widget.json` format Widgets are just small bits of code that run whenever an app that supports them
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
widget bars at the top and bottom of the screen they can add themselves to
the global `WIDGETS` array with:
This is the file that's loaded onto Bangle.js, which gives information ```
about the app. WIDGETS["mywidget"]={
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw:draw // called to draw the widget
};
```
When the widget is to be drawn, `x` and `y` values are set up in `WIDGETS["mywidget"]`
and `draw` can then use `this.x` and `this.y` to figure out where it needs to draw to.
### `app.info` format
This is the file that's **auto-generated** and loaded onto Bangle.js by the App Loader,
and which gives information about the app for the Launcher.
``` ```
{ {
@ -183,20 +210,31 @@ about the app.
``` ```
{ "id": "appid", // 7 character app id { "id": "appid", // 7 character app id
"name": "Readable name", // readable name "name": "Readable name", // readable name
"shortName": "Short name", // short name for launcher
"icon": "icon.png", // icon in apps/ "icon": "icon.png", // icon in apps/
"description": "...", // long description "description": "...", // long description
"type":"...", // optional(if app) - 'app'/'widget'/'launch' "type":"...", // optional(if app) - 'app'/'widget'/'launch'/'bootloader'
"tags": "", // comma separated tag list for searching "tags": "", // comma separated tag list for searching
"readme": "README.md", // if supplied, a link to a markdown-style text file
// that contains more information about this app (usage, etc)
// A 'Read more...' link will be added under the app
"custom": "custom.html", // if supplied, apps/custom.html is loaded in an "custom": "custom.html", // if supplied, apps/custom.html is loaded in an
// iframe, and it must post back an 'app' structure // iframe, and it must post back an 'app' structure
// like this one with 'storage','name' and 'id' set up // like this one with 'storage','name' and 'id' set up
// see below for more info
"interface": "interface.html", // if supplied, apps/interface.html is loaded in an
// iframe, and it may interact with the connected Bangle
// to retrieve information from it
// see below for more info
"allow_emulator":true, // if 'app.js' will run in the emulator, set to true to "allow_emulator":true, // if 'app.js' will run in the emulator, set to true to
// add an icon to allow your app to be tested // add an icon to allow your app to be tested
"storage": [ // list of files to add to storage "storage": [ // list of files to add to storage
{"name":"-appid", // filename to use in storage {"name":"appid.js", // filename to use in storage
"url":"", // URL of file to load (currently relative to apps/) "url":"", // URL of file to load (currently relative to apps/)
"content":"..." // if supplied, this content is loaded directly "content":"..." // if supplied, this content is loaded directly
"evaluate":true // if supplied, data isn't quoted into a String before upload "evaluate":true // if supplied, data isn't quoted into a String before upload
@ -213,6 +251,120 @@ about the app.
* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher` or empty. * tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher` or empty.
* storage is used to identify the app files and how to handle them * storage is used to identify the app files and how to handle them
### `apps.json`: `custom` element
Apps that can be customised need to define a `custom` element in `apps.json`,
which names an HTML file in that app's folder.
When `custom` is defined, the 'upload' button is replaced by a customize
button, and when clicked it opens the HTML page specified in an iframe.
In that HTML file you're then responsible for handling a button
press and calling `sendCustomizedApp` with your own customised
version of what's in `apps.json`:
```
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p><button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../lib/customize.js"></script>
<script>
document.getElementById("upload").addEventListener("click", function() {
sendCustomizedApp({
id : "7chname",
storage:[
{name:"7chname.app.js", content:app_source_code},
{name:"7chname.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
]
});
});
</script>
</body>
</html>
```
This'll then be loaded in to the watch. See [apps/qrcode/grcode.html](the QR Code app)
for a clean example.
### `apps.json`: `interface` element
Apps that create data that can be read back can define a `interface` element in `apps.json`,
which names an HTML file in that app's folder.
When `interface` is defined, a `Download from App` button is added to
the app's description, and when clicked it opens the HTML page specified
in an iframe.
```
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<script src="../../lib/interface.js"></script>
<div id="t">Loading...</div>
<script>
function onInit() {
Puck.eval("E.getTemperature()", temp=> {
document.getElementById("t").innerHTML = temp;
});
}
</script>
</body>
</html>
```
When the page is ready a function called `onInit` is called,
and in that you can call `Puck.write` and `Puck.eval` to get
the data you require from Bangle.js.
See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
### Adding configuration to the "Settings" menu
Apps (or widgets) can add their own settings to the "Settings" menu under "App/widget settings".
To do so, the app needs to include a `settings.js` file, containing a single function
that handles configuring the app.
When the app settings are opened, this function is called with one
argument, `back`: a callback to return to the settings menu.
Example `settings.js`
```js
// make sure to enclose the function in parentheses
(function(back) {
let settings = require('Storage').readJSON('app.settings.json',1)||{};
function save(key, value) {
settings[key] = value;
require('Storage').write('app.settings.json',settings);
}
const appMenu = {
'': {'title': 'App Settings'},
'< Back': back,
'Monkeys': {
value: settings.monkeys||12,
onchange: (m) => {save('monkeys', m)}
}
};
E.showMenu(appMenu)
})
```
In this example the app needs to add both `app.settings.js` and
`app.settings.json` to `apps.json`:
```json
{ "id": "app",
...
"storage": [
...
{"name":"app.settings.js","url":"settings.js"},
{"name":"app.settings.json","content":"{}"}
]
},
```
That way removing the app also cleans up `app.settings.json`.
## Coding hints ## Coding hints
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24" - use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
@ -227,7 +379,17 @@ about the app.
- using `g.setLCDBrightness()` can save you power during long periods with lcd on - using `g.setLCDBrightness()` can save you power during long periods with lcd on
- chaining graphics methodes, eg `g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)` - chaining graphics methods, eg `g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)`
### Misc Notes
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `7chname.json`, then load it at startup.
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.
- Locale is handled by `require("locale")`. An app may create a `locale` file in Storage which is
a module that overwrites Bangle.js's default locale.
### Graphic areas ### Graphic areas
@ -286,7 +448,7 @@ You can use `g.setColor(r,g,b)` OR `g.setColor(16bitnumber)` - some common 16 bi
The [`testing`](testing) folder contains snippets of code that might be useful for your apps. The [`testing`](testing) folder contains snippets of code that might be useful for your apps.
* `testing/colors.js` - 16 bit colors as name value pairs * `testing/colors.js` - 16 bit colors as name value pairs
* `testing/gpstrack.js` - code to store a GPS track in Bagle.js storage and output it back to the console * `testing/gpstrack.js` - code to store a GPS track in Bangle.js storage and output it back to the console
* `testing/map` - code for splitting an image into map tiles and then displaying them * `testing/map` - code for splitting an image into map tiles and then displaying them
## Credits ## Credits

View File

@ -1,55 +0,0 @@
function toJS(txt) {
return JSON.stringify(txt);
}
var AppInfo = {
getFiles : (app,fileGetter) => {
return new Promise((resolve,reject) => {
// Load all files
Promise.all(app.storage.map(storageFile => {
if (storageFile.content)
return Promise.resolve(storageFile);
else if (storageFile.url)
return fileGetter(`apps/${app.id}/${storageFile.url}`).then(content => {
return {
name : storageFile.name,
content : content,
evaluate : storageFile.evaluate
}});
else return Promise.resolve();
})).then(fileContents => { // now we just have a list of files + contents...
// filter out empty files
fileContents = fileContents.filter(x=>x!==undefined);
// then map each file to a command to load into storage
fileContents.forEach(storageFile => {
// check if this is the JSON file
if (storageFile.name[0]=="+") {
storageFile.evaluate = true;
var json = {};
try {
json = JSON.parse(storageFile.content);
} catch (e) {
reject(storageFile.name+" is not valid JSON");
}
if (app.version) json.version = app.version;
json.files = fileContents.map(storageFile=>storageFile.name).join(",");
storageFile.content = JSON.stringify(json);
}
// format ready for Espruino
var js;
if (storageFile.evaluate) {
js = storageFile.content.trim();
if (js.endsWith(";"))
js = js.slice(0,-1);
} else
js = toJS(storageFile.content);
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${js});`;
});
resolve(fileContents);
}).catch(err => reject(err));
});
},
};
if ("undefined"!=typeof module)
module.exports = AppInfo;

1901
apps.json

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -1,12 +1,13 @@
// Create an entry in apps.json as follows: // Create an entry in apps.json as follows:
{ "id": "7chname", { "id": "7chname",
"name": "My app's human readable name", "name": "My app's human readable name",
"shortName":"Short Name",
"icon": "app.png", "icon": "app.png",
"version":"0.01",
"description": "A detailed description of my great app", "description": "A detailed description of my great app",
"tags": "", "tags": "",
"storage": [ "storage": [
{"name":"+7chname","url":"app.json"}, {"name":"7chname.app.js","url":"app.js"},
{"name":"-7chname","url":"app.js"}, {"name":"7chname.img","url":"app-icon.js","evaluate":true}
{"name":"*7chname","url":"app-icon.js","evaluate":true}
] ]
} }

View File

@ -1,5 +0,0 @@
{
"name":"Short Name",
"icon":"*7chname",
"src":"-7chname"
}

View File

@ -0,0 +1 @@
0.01: New Widget!

View File

@ -1,11 +1,13 @@
// Create an entry in apps.json as follows: // Create an entry in apps.json as follows:
{ "id": "7chname", { "id": "7chname",
"name": "My widget's human readable name", "name": "My widget's human readable name",
"shortName":"Short Name",
"icon": "widget.png", "icon": "widget.png",
"version":"0.01",
"description": "A detailed description of my great widget", "description": "A detailed description of my great widget",
"tags": "", "tags": "widget",
"type": "widget",
"storage": [ "storage": [
{"name":"+7chname","url":"widget.json"}, {"name":"7chname.wid.js","url":"widget.js"}
{"name":"-7chname","url":"widget.js"},
] ]
} }

View File

@ -1,16 +1,16 @@
/* run widgets in their own function scope so they don't interfere with /* run widgets in their own function scope so they don't interfere with
currently-running apps */ currently-running apps */
(() => { (() => {
// add the width function draw() {
var xpos = WIDGETPOS.tr-24;/*<the widget width>*/; g.reset(); // reset the graphics context to defaults (color/font/etc)
WIDGETPOS.tr-= 28;/* the widget width plus some extra pixel to keep distance to others */; // add your code
g.drawString("X", this.x, this.y);
// draw your widget at xpos }
function draw() {
// add your code
}
// add your widget
WIDGETS["mywidget"]={draw:draw};
// add your widget
WIDGETS["mywidget"]={
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw:draw // called to draw the widget
};
})() })()

View File

@ -1,4 +0,0 @@
{
"name":"widgetname", "type":"widget",
"src":"-7chname"
}

4
apps/about/ChangeLog Normal file
View File

@ -0,0 +1,4 @@
0.01: New App!
0.02: Update version checker for new filename type
0.03: Actual pixels as of 5 Mar 2020
0.04: Actual pixels as of 9 Mar 2020

1
apps/about/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQqoAHFtovlFxQzOiEQF0QwJFwIwSFyIwIF6YuTGBQule7IvuEp150d5GBS+DSBwtO5wABGA4vUFxvIFwXO44wJF7hcEAAejYJQvYFpAwJF7ejRQgAHF7BcH44tLF47xGF6QtNF8l5vIqFA4gv/R/4vZABwv25ovudYwAHvIvfp+dFxlPFy4wHp9PvPHFo/HFwIvEFqYxHEINP43G4/H5vNAYIHBBgQuaGAgvEAA4vEFzIxDq0zh5YCAAvHh8zqwud/1lssPh+AF4+ABYIPBFroABnUPnPNFwvNnMPnQRDFzgvCh/OdgKMC5vOBIIvEGC4bESAeB5wAErqODGDIbGMAekFwekLw4wWDY9liAoBrpdEiASIFzdloIpBAAkQoITJF7aSERhQvUDhYATF/4v/F74A/AH4A5A="))

33
apps/about/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/about/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1 +1,8 @@
0.02: Modified for use with new bootloader and firmware 0.02: Modified for use with new bootloader and firmware
0.03: add hour ticks, remove timers
0.04: add day-date display
0.07: make date and face bigger
0.08: make dots bigger and date more readable
0.09: center date, remove box around it, internal refactor to remove redundant code.
0.10: remove debug, refactor seconds to show elapsed secs each time app is displayed
0.11: shift face down for widget area, maximize face size, 0 pad single digit date, use locale for date

View File

@ -1,94 +1,151 @@
const p = Math.PI/2; // eliminate ide undefined errors
const PRad = Math.PI/180; let g;
let Bangle;
let intervalRefMin = null; // http://forum.espruino.com/conversations/345155/#comment15172813
let intervalRefSec = null; const locale = require('locale');
const p = Math.PI / 2;
const pRad = Math.PI / 180;
const faceWidth = 100; // watch face radius (240/2 - 24px for widget area)
const widgetHeight=24+1;
let timer = null;
let currentDate = new Date();
const centerX = g.getWidth() / 2;
const centerY = (g.getWidth() / 2) + widgetHeight/2;
let minuteDate = new Date();
let secondDate = new Date();
function seconds(angle, r) { const seconds = (angle) => {
const a = angle*PRad; const a = angle * pRad;
const x = 120+Math.sin(a)*r; const x = centerX + Math.sin(a) * faceWidth;
const y = 120-Math.cos(a)*r; const y = centerY - Math.cos(a) * faceWidth;
g.fillRect(x-1,y-1,x+1,y+1);
} // if 15 degrees, make hour marker larger
function hand(angle, r1,r2) { const radius = (angle % 15) ? 2 : 4;
const a = angle*PRad; g.fillCircle(x, y, radius);
};
const hand = (angle, r1, r2) => {
const a = angle * pRad;
const r3 = 3; const r3 = 3;
g.fillPoly([
120+Math.sin(a)*r1,
120-Math.cos(a)*r1,
120+Math.sin(a+p)*r3,
120-Math.cos(a+p)*r3,
120+Math.sin(a)*r2,
120-Math.cos(a)*r2,
120+Math.sin(a-p)*r3,
120-Math.cos(a-p)*r3]);
}
function drawAll() { g.fillPoly([
Math.round(centerX + Math.sin(a) * r1),
Math.round(centerY - Math.cos(a) * r1),
Math.round(centerX + Math.sin(a + p) * r3),
Math.round(centerY - Math.cos(a + p) * r3),
Math.round(centerX + Math.sin(a) * r2),
Math.round(centerY - Math.cos(a) * r2),
Math.round(centerX + Math.sin(a - p) * r3),
Math.round(centerY - Math.cos(a - p) * r3)
]);
};
const drawAll = () => {
g.clear(); g.clear();
secondDate = minuteDate = new Date(); currentDate = new Date();
// draw hands first // draw hands first
onMinute(); onMinute();
// draw seconds // draw seconds
g.setColor(0,0,0.6); const currentSec = currentDate.getSeconds();
for (let i=0;i<60;i++) // draw all secs
seconds(360*i/60, 90);
for (let i = 0; i < 60; i++) {
if (i > currentSec) {
g.setColor(0, 0, 0.6);
} else {
g.setColor(0.3, 0.3, 1);
}
seconds((360 * i) / 60);
}
onSecond(); onSecond();
}
function onSecond() { };
g.setColor(0,0,0.6);
seconds(360*secondDate.getSeconds()/60, 90);
g.setColor(1,0,0);
secondDate = new Date();
seconds(360*secondDate.getSeconds()/60, 90);
g.setColor(1,1,1);
} const resetSeconds = () => {
g.setColor(0, 0, 0.6);
for (let i = 0; i < 60; i++) {
seconds((360 * i) / 60);
}
};
function onMinute() { const onSecond = () => {
g.setColor(0,0,0); g.setColor(0.3, 0.3, 1);
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -10, 50); seconds((360 * currentDate.getSeconds()) / 60);
hand(360*minuteDate.getMinutes()/60, -10, 82); if (currentDate.getSeconds() === 59) {
minuteDate = new Date(); resetSeconds();
g.setColor(1,1,1); onMinute();
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -10, 50); }
hand(360*minuteDate.getMinutes()/60, -10, 82); g.setColor(1, 0.7, 0.2);
if(minuteDate.getHours() >= 0 && minuteDate.getMinutes() === 0) { currentDate = new Date();
seconds((360 * currentDate.getSeconds()) / 60);
g.setColor(1, 1, 1);
};
const drawDate = () => {
g.reset();
g.setColor(1, 0, 0);
g.setFont('6x8', 2);
const dayString = locale.dow(currentDate, true);
// pad left date
const dateString = (currentDate.getDate() < 10) ? '0' : '' + currentDate.getDate().toString();
const dateDisplay = `${dayString}-${dateString}`;
// console.log(`${dayString}|${dateString}`);
// center date
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
const t = centerY + 37;
g.drawString(dateDisplay, l, t, true);
// console.log(l, t);
};
const onMinute = () => {
if (currentDate.getHours() === 0 && currentDate.getMinutes() === 0) {
g.clear();
resetSeconds();
}
// clear existing hands
g.setColor(0, 0, 0);
// Hour
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
// Minute
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
// get new date, then draw new hands
currentDate = new Date();
g.setColor(1, 0.9, 0.9);
// Hour
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
g.setColor(1, 1, 0.9);
// Minute
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
Bangle.buzz(); Bangle.buzz();
} }
} drawDate();
};
function clearTimers() { const startTimers = () => {
if(intervalRefMin) {clearInterval(intervalRefMin);} timer = setInterval(onSecond, 1000);
if(intervalRefSec) {clearInterval(intervalRefSec);} };
}
function startTimers() { Bangle.on('lcdPower', (on) => {
minuteDate = new Date();
secondDate = new Date();
intervalRefSec = setInterval(onSecond,1000);
intervalRefMin = setInterval(onMinute,60*1000);
drawAll();
}
Bangle.on('lcdPower',function(on) {
if (on) { if (on) {
g.clear(); // g.clear();
Bangle.drawWidgets(); drawAll();
startTimers(); startTimers();
}else { Bangle.drawWidgets();
clearTimers(); } else {
if (timer) {
clearInterval(timer);
}
} }
}); });
g.clear(); g.clear();
resetSeconds();
startTimers();
drawAll();
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
drawAll();
startTimers();
// Show launcher when middle button pressed // Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

View File

@ -1,6 +0,0 @@
{
"name":"Analog Clock","type":"clock",
"icon":"*aclock",
"src":"-aclock",
"sortorder":-10
}

5
apps/alarm/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: New App!
0.02: Fix issues with alarm scheduling
0.03: More alarm scheduling issues
0.04: Tweaks for variable size widget system
0.05: Add alarm.boot.js and move code from the bootloader

60
apps/alarm/alarm.js Normal file
View File

@ -0,0 +1,60 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(alarm.js)' - so let's remove it first!
clearInterval();
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showAlarm(alarm) {
var msg = formatTime(alarm.hr);
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
E.showPrompt(msg,{
title:"ALARM!",
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
alarm.hr += 10/60; // 10 minutes
} else {
alarm.last = (new Date()).getDate();
if (!alarm.rp) alarm.on = false;
}
require("Storage").write("alarm.json",JSON.stringify(alarms));
load();
});
function buzz() {
Bangle.buzz(100).then(()=>{
setTimeout(()=>{
Bangle.buzz(100).then(function() {
if (buzzCount--)
setTimeout(buzz, 3000);
});
},100);
});
}
buzz();
}
// Check for alarms
var day = (new Date()).getDate();
var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
var alarms = require("Storage").readJSON("alarm.json",1)||[];
var active = alarms.filter(a=>a.on&&(a.hr<hr)&&(a.last!=day));
if (active.length) {
// if there's an alarm, show it
active = active.sort((a,b)=>a.hr-b.hr);
showAlarm(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

1
apps/alarm/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkGswAhiMRCCAREAo4eHBIQLEAgwYHsIJDiwHB5gACBpIhHCoYZEGA4gFCw4ABGA4HEjgXJ4IXGAwcUB4VEmf//8zogICoJIFAodMBoNDCoIADmgJB4gXIFwXDCwoABngwFC4guB4k/CQXwh4EC+YMCC44iBp4qDC4n/+gNBC41sEIJCEC4v/GAPGC4dhXYRdFC4xhCCYIXCdQRdDC5HzegQXCsxGHC45IDCwQXCUgwXHJAIXGRogXJSIIXcOw4XIPAYXcBwv/mEDBAwXOgtQC65QGC5vzoEAJAx3Nmk/mEABIiPN+dDAQIwFC4zXGFwKRCGAjvMFwQECGAgXI4YuGGAUvAgU8C4/EFwwGCAgdMC4p4EFwobFOwoXDJAIoEAApGBC4xIEABJGHGAapEAAqNBFwwXD4heI+YuBC5BIBVQhdHIw4wD5inFS4IKCCxFmigNCokzCoMzogICoIWIsMRjgPCAA3BiMWC48RBQIXJEgMRFxAJCCw4lEC44IECooOIBAaBJKwhgIAH4ACA=="))

104
apps/alarm/app.js Normal file
View File

@ -0,0 +1,104 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var alarms = require("Storage").readJSON("alarm.json",1)||[];
/*alarms = [
{ on : true,
hr : 6.5, // hours + minutes/60
msg : "Eat chocolate",
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
rp : true, // repeat
}
];*/
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showMainMenu() {
const menu = {
'': { 'title': 'Alarms' },
'New Alarm': ()=>editAlarm(-1)
};
alarms.forEach((alarm,idx)=>{
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += " (repeat)";
menu[txt] = function() {
editAlarm(idx);
};
});
menu['< Back'] = ()=>{load();};
return E.showMenu(menu);
}
function editAlarm(alarmIndex) {
var newAlarm = alarmIndex<0;
var hrs = 12;
var mins = 0;
var en = true;
var repeat = true;
if (!newAlarm) {
var a = alarms[alarmIndex];
hrs = 0|a.hr;
mins = Math.round((a.hr-hrs)*60);
en = a.on;
repeat = a.rp;
}
const menu = {
'': { 'title': 'Alarms' },
'Hours': {
value: hrs,
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Minutes': {
value: mins,
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
},
'Enabled': {
value: en,
format: v=>v?"On":"Off",
onchange: v=>en=v
},
'Repeat': {
value: en,
format: v=>v?"Yes":"No",
onchange: v=>repeat=v
}
};
function getAlarm() {
var hr = hrs+(mins/60);
var day = 0;
// If alarm is for tomorrow not today (eg, in the past), set day
if (hr < getCurrentHr())
day = (new Date()).getDate();
// Save alarm
return {
on : en, hr : hr,
last : day, rp : repeat
};
}
if (newAlarm) {
menu["> New Alarm"] = function() {
alarms.push(getAlarm());
require("Storage").write("alarm.json",JSON.stringify(alarms));
showMainMenu();
};
} else {
menu["> Save"] = function() {
alarms[alarmIndex] = getAlarm();
require("Storage").write("alarm.json",JSON.stringify(alarms));
showMainMenu();
};
}
menu['< Back'] = showMainMenu;
return E.showMenu(menu);
}
showMainMenu();

BIN
apps/alarm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

24
apps/alarm/boot.js Normal file
View File

@ -0,0 +1,24 @@
// check for alarms
(function() {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));
if (active.length) {
active = active.sort((a,b)=>a.hr-b.hr);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]")
} else {
var t = 3600000*(active[0].hr-hr);
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
})()

11
apps/alarm/widget.js Normal file
View File

@ -0,0 +1,11 @@
(() => {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
alarms = alarms.filter(alarm=>alarm.on);
if (!alarms.length) return; // no alarms, no widget!
delete alarms;
// add the widget
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
g.setColor(-1);
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
}};
})()

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4Az4/HDDAACCywaSCpgeLBQOjzowRIxZPMBIWj0YvjNRKQSCpIWBEBq/VF6hrKdpwuKABYvXFywlDXjw5aF1gvKF0gv5F0ovvFxQxkF9ztLF8ItJF5XGAAIrfeJYtB4D7TFyQlEFwg8INgZtFF6wuCF4wAFBoPAF7ouNF8AfCF56ZFFyofEF9YgCF5z6GF9y+TC4SAFF9ZgNBgovZMAeczgRJBYovXMAtPAAIQIBYouXDAWjEYUrGBMrBYgvY/15vNVCtAADqoACCs4A/AH4A/AH4A/ABY") require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4Az4/HDDAACCywaSCpgeLBQOjzowRIxZPMBIWj0YvjNRKQSCpIWBEBq/VF6hrKdpwuKABYvXFywlDXjw5aF1gvKF0gv5F0ovvFxQxkF9ztLF8ItJF5XGAAIrfeJYtB4D7TFyQlEFwg8INgZtFF6wuCF4wAFBoPAF7ouNF8AfCF56ZFFyofEF9YgCF5z6GF9y+TC4SAFF9ZgNBgovZMAeczgRJBYovXMAtPAAIQIBYouXDAWjEYUrGBMrBYgvY/15vNVCtAADqoACCs4A/AH4A/AH4A/ABY"))

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/ACXJ5PKAQfKAAPJB4nDAAIsaEwQAJ5IrCAAYbFGyBWBFyooCGxQABPAyDEABYuHGxInFAAQGGLyoIGLxQvCFyAvJBIzjKdB6OLagYwDc5K/DFyIVBAAprHF5IsTHaAuKRoSOSABwvMXyZhPF5iNfF9nJp9PGB4vh5PD3u8X86PCFwO8AAS/mGQe9zAACF/CPdF4aPD3rwmGAu9FxRghegQuKL8IvBFxYwhFx6QdFpxedXIIuQLpHJ4YdCBoQDDFQosRLxAaB4YAEIRIQDFyQvEFhYwGCQgvX/wcOCYYuWDYSmCF6CfEF6mlz96vX+z+evVPCZwABCJYAJvVVAAVPAAYTNCJoAJFwYvPCY5gUAH4A/AH4A/AH4A2") require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/ACXJ5PKAQfKAAPJB4nDAAIsaEwQAJ5IrCAAYbFGyBWBFyooCGxQABPAyDEABYuHGxInFAAQGGLyoIGLxQvCFyAvJBIzjKdB6OLagYwDc5K/DFyIVBAAprHF5IsTHaAuKRoSOSABwvMXyZhPF5iNfF9nJp9PGB4vh5PD3u8X86PCFwO8AAS/mGQe9zAACF/CPdF4aPD3rwmGAu9FxRghegQuKL8IvBFxYwhFx6QdFpxedXIIuQLpHJ4YdCBoQDDFQosRLxAaB4YAEIRIQDFyQvEFhYwGCQgvX/wcOCYYuWDYSmCF6CfEF6mlz96vX+z+evVPCZwABCJYAJvVVAAVPAAYTNCJoAJFwYvPCY5gUAH4A/AH4A/AH4A2"))

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AGesAAQuuGAQ1jFQoAKF1wwdFyQycF6wwVFi4wWEiOBq1WqoABvQHBvWALsl6p4ADF4IIBGwSNjMAJdCAAVVGwQwPXjWBFoMrF4IwOFzVWp94lV4vIwNFzS8CvGi0YFCGBSNadohdCF9gAGdsgvuGJTvkGAgABFxv+wAwcAAQsLAAVPF9lPAAOIF1tWF7qMOp4DBxAABXtIADGAWB0oupGAeAvQABwJmGAwmlwQuZAAQuCF4SYDAgRtBBoeswKsDF7gsEF4+BqovfAANWF5VWFwIvZGAf+EAYuDBooOEF8ANJF/4vREIQv7BxIvZEIwvvBwQveEIIEDF9QA/AH4A/AH4ASA=") require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AGesAAQuuGAQ1jFQoAKF1wwdFyQycF6wwVFi4wWEiOBq1WqoABvQHBvWALsl6p4ADF4IIBGwSNjMAJdCAAVVGwQwPXjWBFoMrF4IwOFzVWp94lV4vIwNFzS8CvGi0YFCGBSNadohdCF9gAGdsgvuGJTvkGAgABFxv+wAwcAAQsLAAVPF9lPAAOIF1tWF7qMOp4DBxAABXtIADGAWB0oupGAeAvQABwJmGAwmlwQuZAAQuCF4SYDAgRtBBoeswKsDF7gsEF4+BqovfAANWF5VWFwIvZGAf+EAYuDBooOEF8ANJF/4vREIQv7BxIvZEIwvvBwQveEIIEDF9QA/AH4A/AH4ASA="))

View File

@ -13,7 +13,7 @@ function next(e) {
} while (current && current==last); } while (current && current==last);
g.clear(); g.clear();
var n = 1 + (0|(Math.random()*3.9)); var n = 1 + (0|(Math.random()*3.9));
var img = require("Storage").read("*"+current); var img = require("Storage").read("animals-"+current+".img");
if (n==1) if (n==1)
g.drawImage(img,120,120,{scale:4,rotate:Math.random()-0.5}); g.drawImage(img,120,120,{scale:4,rotate:Math.random()-0.5});
else else

View File

@ -1,5 +0,0 @@
{
"name":"Animals Game", "type":"app",
"icon":"*animals",
"src":"-animals"
}

1
apps/astrocalc/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Create astrocalc app

View File

@ -0,0 +1,348 @@
/**
* Inspired by: https://www.timeanddate.com
*/
const SunCalc = require("suncalc.js");
function drawMoon(phase, x, y) {
const moonImgFiles = [
"new",
"waxing-crescent",
"first-quarter",
"waxing-gibbous",
"full",
"waning-gibbous",
"last-quarter",
"waning-crescent",
];
img = require("Storage").read(`${moonImgFiles[phase]}.img`);
// image width & height = 92px
g.drawImage(img, x - parseInt(92 / 2), y);
}
// linear interpolation between two values a and b
// u controls amount of a/b and is in range [0.0,1.0]
function lerp(a,b,u) {
return (1-u) * a + u * b;
}
function titlizeKey(key) {
return (key[0].toUpperCase() + key.slice(1)).match(/[A-Z][a-z]+/g).join(" ");
}
function dateToTimeString(date) {
const hrs = ("0" + date.getHours()).substr(-2);
const mins = ("0" + date.getMinutes()).substr(-2);
const secs = ("0" + date.getMinutes()).substr(-2);
return `${hrs}:${mins}:${secs}`;
}
function drawTitle(key) {
const fontHeight = 16;
const x = 0;
const x2 = g.getWidth() - 1;
const y = fontHeight + 26;
const y2 = g.getHeight() - 1;
const title = titlizeKey(key);
g.setFont("6x8", 2);
g.setFontAlign(0,-1);
g.drawString(title,(x+x2)/2,y-fontHeight-2);
g.drawLine(x,y-2,x2,y-2);
}
/**
* @params {Number} angle Angle of point around a radius
* @params {Number} radius Radius of the point to be drawn, default 2
* @params {Object} color Color of the point
* @params {Number} color.r Red 0-1
* @params {Number} color.g Green 0-1
* @params {Number} color.b Blue 0-1
*/
function drawPoint(angle, radius, color) {
const pRad = Math.PI / 180;
const faceWidth = 80; // watch face radius
const centerPx = g.getWidth() / 2;
const a = angle * pRad;
const x = centerPx + Math.sin(a) * faceWidth;
const y = centerPx - Math.cos(a) * faceWidth;
if (!radius) radius = 2;
g.setColor(color.r, color.g, color.b);
g.fillCircle(x, y + 20, radius);
}
function drawPoints() {
const startColor = {r: 140, g: 255, b: 255}; // light blue
const endColor = {r: 0, g: 0, b: 140}; // dark turquoise
const steps = 60;
const step_u = 1.0 / (steps / 2);
let u = 0.0;
for (let i = 0; i < steps; i++) {
const colR = lerp(startColor.r, endColor.r, u) / 255;
const colG = lerp(startColor.g, endColor.g, u) / 255;
const colB = lerp(startColor.b, endColor.b, u) / 255;
const col = {r: colR, g: colG, b: colB};
if (i >= 0 && i <= 30) {
u += step_u;
} else {
u -= step_u;
}
drawPoint((360 * i) / steps, 2, col);
}
}
function drawData(title, obj, startX, startY) {
g.clear();
drawTitle(title);
let xPos, yPos;
if (typeof(startX) === "undefined" || startX === null) {
// Center text
g.setFontAlign(0,-1);
xPos = (0 + g.getWidth() - 2) / 2;
} else {
xPos = startX;
}
if (typeof(startY) === "undefined") {
yPos = 5;
} else {
yPos = startY;
}
g.setFont("6x8", 1);
Object.keys(obj).forEach((key) => {
g.drawString(`${key}: ${obj[key]}`, xPos, yPos += 20);
});
g.flip();
}
function drawMoonPositionPage(gps, title) {
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
const pageData = {
Azimuth: pos.azimuth.toFixed(2),
Altitude: pos.altitude.toFixed(2),
Distance: `${pos.distance.toFixed(0)} km`,
"Parallactic Ang": pos.parallacticAngle.toFixed(2),
};
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
drawData(title, pageData, null, 80);
drawPoints();
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1});
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BTN3, {repeat: false, edge: "falling"});
}
function drawMoonIlluminationPage(gps, title) {
const phaseNames = [
"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
"Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent",
];
const phase = SunCalc.getMoonIllumination(new Date());
const pageData = {
Phase: phaseNames[phase.phase],
};
drawData(title, pageData, null, 35);
drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2);
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BTN3, {repease: false, edge: "falling"});
}
function drawMoonTimesPage(gps, title) {
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
const pageData = {
Rise: dateToTimeString(times.rise),
Set: dateToTimeString(times.set),
};
drawData(title, pageData, null, 105);
drawPoints();
// Draw the moon rise position
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
// Draw the moon set position
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
let m = setWatch(() => {
let m = moonIndexPageMenu(gps);
}, BTN3, {repease: false, edge: "falling"});
}
function drawSunShowPage(gps, key, date) {
const pos = SunCalc.getPosition(date, gps.lat, gps.lon);
const hrs = ("0" + date.getHours()).substr(-2);
const mins = ("0" + date.getMinutes()).substr(-2);
const secs = ("0" + date.getMinutes()).substr(-2);
const time = `${hrs}:${mins}:${secs}`;
const azimuth = Number(pos.azimuth.toFixed(2));
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
const altitude = Number(pos.altitude.toFixed(2));
const pageData = {
Time: time,
Altitude: altitude,
Azimumth: azimuth,
Degrees: azimuthDegrees
};
drawData(key, pageData, null, 85);
drawPoints();
// Draw the suns position
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 0});
m = setWatch(() => {
m = sunIndexPageMenu(gps);
}, BTN3, {repeat: false, edge: "falling"});
return null;
}
function sunIndexPageMenu(gps) {
const sunTimes = SunCalc.getTimes(new Date(), gps.lat, gps.lon);
const sunMenu = {
"": {
"title": "-- Sun --",
},
"Current Pos": () => {
m = E.showMenu();
drawSunShowPage(gps, "Current Pos", new Date());
},
};
Object.keys(sunTimes).sort().reduce((menu, key) => {
const title = titlizeKey(key);
menu[title] = () => {
m = E.showMenu();
drawSunShowPage(gps, key, sunTimes[key]);
};
return menu;
}, sunMenu);
sunMenu["< Back"] = () => m = indexPageMenu(gps);
return E.showMenu(sunMenu);
}
function moonIndexPageMenu(gps) {
const moonMenu = {
"": {
"title": "-- Moon --",
},
"Times": () => {
m = E.showMenu();
drawMoonTimesPage(gps, "Times");
},
"Position": () => {
m = E.showMenu();
drawMoonPositionPage(gps, "Position");
},
"Illumination": () => {
m = E.showMenu();
drawMoonIlluminationPage(gps, "Illumination");
},
"< Back": () => m = indexPageMenu(gps),
};
return E.showMenu(moonMenu);
}
function indexPageMenu(gps) {
const menu = {
"": {
"title": "Select",
},
"Sun": () => {
m = sunIndexPageMenu(gps);
},
"Moon": () => {
m = moonIndexPageMenu(gps);
},
"< Exit": () => { load(); }
};
return E.showMenu(menu);
}
/**
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
*/
function drawGPSWaitPage() {
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))
g.clear();
g.drawImage(img, 100, 50);
g.setFont("6x8", 1);
g.drawString("Astrocalc v0.01", 80, 105);
g.drawString("Locating GPS", 85, 140);
g.drawString("Please wait...", 80, 155);
g.flip();
const DEBUG = false;
if (DEBUG) {
const gps = {
"lat": 56.45783133333,
"lon": -3.02188583333,
"alt": 75.3,
"speed": 0.070376,
"course": NaN,
"time":new Date(),
"satellites": 4,
"fix": 1
};
m = indexPageMenu(gps);
return;
}
Bangle.on('GPS', (gps) => {
if (gps.fix === 0) return;
Bangle.setGPSPower(0);
Bangle.buzz();
Bangle.setLCDPower(true);
m = indexPageMenu(gps);
});
}
function init() {
Bangle.setGPSPower(1);
drawGPSWaitPage();
}
let m;
init();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI1ygf4BZM/BZMD//wCxP/8AWJ/+ACxP+CxQ6ICwP/4AWJERAWCEQ4WCERAWCEQ4WDOg4WCNA4WD/gWKRYwWDHI4WDHIwWDHI4WDHIwWEOYwWDHIwWEKAwWD/4WKKAwWEKAoWEYgwWPM4wWEM4oWQM4oWEPwwWbPwoWESowW/C34WOZ1vACxP8Cyv4CxWACyoKFCwiUFCwhmGCwh9FCwhmGCwhmFCwhPGCwgKFCwg4GCwZPGCwg4GCwY4GCwgKGCwY4GCwZxGCwjBFCwghHCwQhHCwYhHCwQhHCwRlHCwSHHCwYKICwI3HCwQKJAFAA=="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgJC/AD8B//4BRILJBQP/+AKGn4LC4AKFh4KC/4KFgYKD/gLFv4LD8AKEj4KD/+AEJAiGEIgiFIYhFFOAQADOghlDNA0HBQv+Q4wADRYZaFLgg4GHIg4GHIY4GHIhxFOYhxGOYgKHKARPHKARPHKAZPHKATBFYgoWKMw5nDMw5nCCyx9IPwQKIPwIW/C34WJZ1sDBQ/8CwM/BY/ACxkfBY+AgEBBQ/4CwJ+IBQJ+IPoJnIMwRnIMwJQIJ4RQIJ4JQIJ4RQIBQQ5HHAQ5HHAY5HHARzHOIRzHOIbEHYIIACLgpaDEQwhFEQohEIopDENAplERYwKGOgZwEBYoKIAH4AXA=="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI0xgP8BRP/4ALI/4WJv4WJj4WJg//CxA3BCxM/CxIhCCw4hCCxAhCCw4hCCxAKCCw5lBCxEDCxSHBCxA4DCw4KCCw44DCww4DCw5xCCw44DCw5PDCw0PCxQKDCwxPDCwzBDCyRmECwxmDCyRmDCwx9ECzoKDCwyUEC34W/CyDOtn4WJgYWVgIWKj4WVPwgWFSogWGM4gWGPwYWGM4gWGM4YWGKAgWGKAYWGHIgWGKAYWHHIYWGHIYWHHIYWGHIYWHOYYWHYgQWHEQYWHEQQWIEQQWHEQQWINAQWIRYIWIOgQWIHQIWJBYIWJAFI="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgIGDh///4RHBQQLHg4KC/wKFgIKC//4BYt/BYfgBQkfBQf/wAsHFw4HCBwXwBQc/AwYLB4AhEIARIBEQn//gECgYiEIYJ2FIoQQBE4YzBDgd/NoguBNAUPKoo/BB4YhEEQIdCAYYiECQMHUwwHDEIweBLgMPWIwiBAQSlENwQTBDIQAFFQMDHAw5BOYN/HAwfB8ANCAAofCHA45B+EPHA4UBKQQAGMgMfUYQAFv+DJ45QCn5PHKAPDJ45QB/hmICwPnT4yhC/1/Mw5nBCxZmIM4P/PpB+BC34WEVZCsB/7CIYYIWWOX4WbfiwWL/gKHgf+n/ABY8/4YWJ/k/VhF/4LDIg/4j5nI/+APxEP+EPM48BCgN/KA5CBg5QHMwINCJ4/AgY5Hh4fBj45GHAKeBAQSfFMgIZCHAoqCv45GA4QOBEQsfDwQDDEIgSC/4iFv6dCg4iFj60Dn4iEEIKRCL4K5E/5uDh4QDDgKFEv4uDj4/EE4IRCDYIzEAwIvBAQKnFEQIADMIhFBAAayFNAIACMoZtDBYa9GFwbrHBQR2EBYoKEA=="))

328
apps/astrocalc/suncalc.js Normal file
View File

@ -0,0 +1,328 @@
/*
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
*/
(function () { 'use strict';
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; }
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only.
h = 0; // if h = -0.08901179 a div/0 would occur.
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
function sunCoords(d) {
var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
return {
dec: declination(L, 0),
ra: rightAscension(L, 0)
};
}
var SunCalc = {};
// calculates sun position for a given date and latitude/longitude
SunCalc.getPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = sunCoords(d),
H = siderealTime(d, lw) - c.ra;
return {
azimuth: azimuth(H, phi, c.dec),
altitude: altitude(H, phi, c.dec)
};
};
// sun times configuration (angle, morning name, evening name)
var times = SunCalc.times = [
[-0.833, 'sunrise', 'sunset' ],
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
[ -6, 'dawn', 'dusk' ],
[ -12, 'nauticalDawn', 'nauticalDusk'],
[ -18, 'nightEnd', 'night' ],
[ 6, 'goldenHourEnd', 'goldenHour' ]
];
// adds a custom time to the times config
SunCalc.addTime = function (angle, riseName, setName) {
times.push([angle, riseName, setName]);
};
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
SunCalc.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: new Date(fromJulian(Jnoon)),
nadir: new Date(fromJulian(Jnoon - 0.5))
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2));
result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2));
}
return result;
};
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
F = rad * (93.272 + 13.229350 * d), // mean distance
l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
dt = 385001 - 20905 * cos(M); // distance to the moon in km
return {
ra: rightAscension(l, b),
dec: declination(l, b),
dist: dt
};
}
SunCalc.getMoonPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c
SunCalc.getMoonIllumination = function (date) {
let month = date.getMonth();
let year = date.getFullYear();
let day = date.getDate();
let c = 0;
let e = 0;
let jd = 0;
let b = 0;
if (month < 3) {
year--;
month += 12;
}
++month;
c = 365.25 * year;
e = 30.6 * month;
jd = c + e + day - 694039.09; // jd is total days elapsed
jd /= 29.5305882; // divide by the moon cycle
b = parseInt(jd); // int(jd) -> b, take integer part of jd
jd -= b; // subtract integer part to leave fractional part of original jd
b = Math.round(jd * 8); // scale fraction from 0-8 and round
if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0
return {phase: b};
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
var t = date;
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
else if (typeof define === 'function' && define.amd) define(SunCalc);
else global.SunCalc = SunCalc;
}());

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgJC/ABHgBRN8BRMfwAKIg/4CxP/BRM/HBMH/wKIgP/4AhJ/ghJ/5PJ/5PJj4WJgf/+AWIv5mJHAIWJ/5mJHAJ9IHAIWJn59JHAJ9JJ4IWIh4WK/4WJJ4KUIYIKUJJ4IWIMwIWgMwIWIPoLCJCwLCICxYKBCxCUBC34W/Cya3WCxr8In78JgYWhj4WJgIWKPwP8SpXAM5IWJPwIWIKAIWJM4PgKBP+CxBQBCxA5CBRBQBYZA5CBRA5BSpA5CSpA5BCxJzBPxDEBPxIiBM5MDPxJFBM5IiBKBMBKBKLBKBMAhwKJAH4ABA="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI0xgP/wAKJ/wWI///+AKHv4LBEQ8fBQP8BQ0HBQP/8A3HAAQWGn4KCHIwhDHIwhE/AhJ//AEJJQGBQZQGMoQABRQsDCwhQFQ4RnHHAgWGBQhnFHAhnFHAoWFOIhnFHAp+FJ4oWEh4WKBQp+EJ4qVEYIgWRMwwWEMwoWLVghmFVgh9GCzYKGCwaUGC34W/CxzOtn4WJgYKF/wWK8AKCgIWKj4WVPwwWDSo38BQZnG4B+JCwhnGCwhnF/AKDKA2AKBIWEHIwKEKAqrDHI4KEHIp9EHIqUEHIxmEOYp9EYgxmEEQpmFEQoKFEQhmFEQhPGNAhPFRYg4GOggKHHQSIFBYghIAFQ="))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgJC/ABdwBRMD8ALJj+ABREB/wWJh/wBZN/4AKIg4iKn/4KBP/ERMfERMB/5FJj//NBP//hnJ/6LJ/45Jg45Kv45JCwI5Jn5zJPwI5JCwJQICwP/CxRQISoJQJSoLEICwRQICwJnICzJnIYYJ+JCzB+ICwKVJC34W/CxbOffgIWIfgXACxP8Cyv4CxWACyUDPpU/ShIWBPpIWBPpEHMxMAv5mJCwJPICwQKIYQI4IYQJPJCwI4ISgI4JSgIKICwI4Jn5xJSgLBIMwIhJg4hJMwIKJj4hJgJlJgE+BRMHBRIA+A"))

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("rlcgI1yj/4BREH/4LJ/4LJj4LB8AKGgYKB/+ABY1/BQP+BQ0PCwQuHBQX/4A4IEQ8BCwYiGn4iJJ4YiHJ4QAB+CIGAAZoFBQn8MxCLHBQg5FMwY5GMwg5GCwo5EMwhzGPog5FCwxQECwv/PpJQFSghQFCwzEECyJnECwxnDVYoWFBQpnECwx+ECzp+DCwyVEC34W/CyDOt4AKCg4KF/gWDv4WQ/AWKwAWVBQcDShMAn5mJCwx9DCwxmEgJmJgEfJ5IWGBQasGHAisFJ4gWGHAh+FHAiVGBQhnFHAp+EOIhnGYIZnGEIpQEEIxnEEIpQEEIxQDMoo5EQ4o5FFgyKDBRAiBBRAApA="))

View File

@ -1,5 +0,0 @@
{
"name":"Asteroids!","type":"app",
"icon":"*astroid",
"src":"-astroid"
}

View File

@ -0,0 +1 @@
0.01: Initial version of Balltastic released! Happy!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEogAIkUzmciBpIVIkYWBAAUyCx0hiIXFAAMkCxhUBC4fzDAYWLiAXFAAP//5KKoMRC4UTC4k/DAPzJJERiKcCC5H/GA4uBWwp6DC4YwHCwMBDI0SMAYwHoIWBiXxdIwYCMJCjBiM/C46VDC4M/GAkRgMf//ySAQAEgKrDC4lBgMCHIQXHSwxICIwIuBAAIXIGAYABiQXBkEBTYcgC473FkQXBiETTQZ4IgECC4cholCiJGDMAIXIWgIXCmMkC4JGDJBbEDC4UACwn/mAtGSYsxilCgIXFSAqDBkMRiIFBkcxiUiC4sxXowIBC4QGBkIXBiJ2EFwsDBIPyC4ILBgMRiUyiCmJgSCC+YXDgAXDR4YuEcAn/MAIXEmcgBoXyFwjIEMAQXFkIOCUgoXF+J3CC4cxBwR1IQQx3BkUzmUSBQKkFC5IuBkVDJAJeGRwLhHFwUkC4Mxl6lFC48gFwYXCmcTOwomBC4swYIMikU0C4UxkJ3FC40xFoIXCogXBmaxDC5MyCwUiogXDmIXTJASSBC4kRU4oXDkgXFmQwDNwIWEBoIXFJAYKBZggWFC4YWCC4g7BkIWBkYWBBYYXCkYXDJAYjDkQUEEYZGEGA4XIIwwwGDAQuOGAomCFo4uGGARoBE4ZOGFxAABBwgAICxAABCyxJBGJJFJJRgVNPggsMA="))

186
apps/balltastic/app.js Normal file
View File

@ -0,0 +1,186 @@
Bangle.setLCDBrightness(1);
Bangle.setLCDMode("doublebuffered");
let points = 0;
let level = 1;
let levelSpeedStart = 0.8;
let nextLevelPoints = 20;
let levelSpeedFactor = 0.2;
let counterWidth = 10;
let gWidth = g.getWidth() - counterWidth;
let gHeight = g.getHeight();
let counter = 160;
let counterMax = 160;
let ballDims = 20;
let ballx = g.getWidth() / 2 - ballDims;
let bally = g.getHeight() / 2 - ballDims;
let dotx = g.getWidth() / 2;
let doty = g.getWidth() / 2;
let ballBuzzTime = 5;
let ballSpeedFactor = 40;
let redrawspeed = 5;
let dotwidth = 5;
let running = false;
let drawInterval;
let xBuzzed = false;
let yBuzzed = false;
let BALL = require("heatshrink").decompress(
atob(
"ikUyAROvkQ3v4405AIYHBGq9KpMhktz1/W7feAJAtBEZ9jhkhs0ZgkQ8lKxW+jAdB516627E4X8AIPWzelmolKlpJBjMFEYIpC4kQ0YBBqWKynTFYPe7gpE3ec6gnHkNFrXL7372u2E4WjhGCAIliqWrUIPeKoIpB7h9HoUoqWq999///FIJ3BhGDEIIBBgFBAoWCoUI3vY62aQIW7ymSJooLBEoIADwkQEYVhEoInEGIOjR4O1y/OrIrBUYdr198iH/74nF88cE4gpCA4MY8k59CzBAINrx2164nBtduufPWYIlF++/xkxNoMAAIJPBoSdB52a30ZkNGE4IvBoUpwkxLIOMyWEmAmE7+MqKbEsLLBH4P3zw1BAYJFBFIMY8sQ4cx44nB0tVHYITBEoO967lDgDDC1tVQ4QBD37xBjMmJ4I3BE4IxBPoOMuSrBHYL1BJYbrDvfPLoYBD889jMlEoMhkpJBwkRE4O+jB7B405LoJPEYYUx0xPG7/3vxvBmOnrXsdIOc6jxBE4JfBvfwHIafDFoMRgh3H99+zsUDIOMqWU2YlBAAO1/AnBToN76EhgpTBFYKPBGIIhBEovOrWliuc2YlBE4oABE4etu2UyVrpqJBMoKvBEIPnjvWze97ATBE4YPBEopRC64BC27nBzn0znTAIOlimtq21y4BCEoM1HYOMqIVBE44AB0tVCYIBEigVBE4U1GYIFBymywkwEoJzHABIRBMIIXBWoIDCqOEmOEiABCmIjPAA51BFoVSEoUwAIIZNA"
)
);
function reset() {
g.clear();
level = 1;
points = 0;
ballx = g.getWidth() / 2 - ballDims;
bally = g.getHeight() / 2 - ballDims;
counter = counterMax;
createRandomDot();
drawInterval = setInterval(play, redrawspeed);
running = true;
}
function collide() {
try {
Bangle.buzz(ballBuzzTime, 0.8);
} catch (e) {}
}
function createRandomDot() {
dotx = Math.floor(
Math.random() * Math.floor(gWidth - dotwidth / 2) + dotwidth / 2
);
doty = Math.floor(
Math.random() * Math.floor(gHeight - dotwidth / 2) + dotwidth / 2
);
}
function checkIfDotEaten() {
if (
ballx + ballDims > dotx &&
ballx <= dotx + dotwidth &&
bally + ballDims > doty &&
bally <= doty + dotwidth
) {
collide();
createRandomDot();
counter = counterMax;
points++;
if (points % nextLevelPoints == 0) {
level++;
}
}
}
function drawLevelText() {
g.setColor("#26b6c7");
g.setFontAlign(0, 0);
g.setFont("4x6", 5);
g.drawString("Level " + level, 120, 80);
}
function draw() {
//bg
g.setColor("#71c6cf");
g.fillRect(0, 0, g.getWidth(), g.getHeight());
//counter
drawCounter();
//draw level
drawLevelText();
//dot
g.setColor("#ff0000");
g.fillCircle(dotx, doty, dotwidth);
//ball
g.drawImage(BALL, ballx, bally);
g.flip();
}
function drawCounter() {
g.setColor("#000000");
g.fillRect(g.getWidth() - counterWidth, 0, g.getWidth(), gHeight);
if(counter < 40 ) g.setColor("#fc0303");
else if (counter < 80 ) g.setColor("#fc9803");
else g.setColor("#0318fc");
g.fillRect(
g.getWidth() - counterWidth,
gHeight,
g.getWidth(),
gHeight - counter
);
}
function checkCollision() {
if (ballx < 0) {
ballx = 0;
if (!xBuzzed) collide();
xBuzzed = true;
} else if (ballx > gWidth - ballDims) {
ballx = gWidth - ballDims;
if (!xBuzzed) collide();
xBuzzed = true;
} else {
xBuzzed = false;
}
if (bally < 0) {
bally = 0;
if (!yBuzzed) collide();
yBuzzed = true;
} else if (bally > gHeight - ballDims) {
bally = gHeight - ballDims;
if (!yBuzzed) collide();
yBuzzed = true;
} else {
yBuzzed = false;
}
}
function count() {
counter -= levelSpeedStart + level * levelSpeedFactor;
if (counter <= 0) {
running = false;
clearInterval(drawInterval);
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50);
}
}
function accel(values) {
ballx -= values.x * ballSpeedFactor;
bally -= values.y * ballSpeedFactor;
}
function play() {
if (running) {
accel(Bangle.getAccel());
checkCollision();
checkIfDotEaten();
count();
draw();
}
}
setTimeout(() => {
reset();
drawInterval = setInterval(play, redrawspeed);
setWatch(
() => {
if(!running) reset();
},
BTN1,
{ repeat: true }
);
running = true;
}, 10);

BIN
apps/balltastic/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

4
apps/barclock/ChangeLog Normal file
View File

@ -0,0 +1,4 @@
0.01: Created Bar Clock
0.02: Apply locale, 12-hour setting
0.03: Fix dates drawing over each other at midnight
0.04: Small bugfix

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/AD8Mgfwh/AhgFFngHBOIM8AovMDIXA5gFFDoUAmYjDAocMSoMz/4FF//P/g1CAopTLDAIABwAFGAH4AfA"))

166
apps/barclock/clock-bar.js Normal file
View File

@ -0,0 +1,166 @@
/* jshint esversion: 6 */
/**
* A simple digital clock showing seconds as a bar
**/
{
// Check settings for what type our clock should be
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
let locale = require('locale')
{ // add some more info to locale
let date = new Date()
date.setFullYear(1111)
date.setMonth(1, 3) // februari: months are zero-indexed
const localized = locale.date(date, true)
locale.dayFirst = /3.*2/.test(localized)
locale.hasMeridian = (locale.meridian(date) !== '')
}
const screen = {
width: g.getWidth(),
height: g.getWidth(),
middle: g.getWidth() / 2,
center: g.getHeight() / 2,
}
// hardcoded "settings"
const settings = {
time: {
color: -1,
font: '6x8',
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
middle: screen.middle,
center: screen.center,
ampm: {
color: -1,
font: '6x8',
size: 2,
},
},
date: {
color: -1,
font: 'Vector',
size: 20,
middle: screen.height - 20, // at bottom of screen
center: screen.center,
},
bar: {
color: -1,
top: 155, // just below time
thickness: 6, // matches 24h time "pixel" size
},
}
const SECONDS_PER_MINUTE = 60
const timeText = function (date) {
if (!is12Hour) {
return locale.time(date, true)
}
const date12 = new Date(date.getTime())
const hours = date12.getHours()
if (hours === 0) {
date12.setHours(12)
} else if (hours > 12) {
date12.setHours(hours - 12)
}
return locale.time(date12, true)
}
const ampmText = function (date) {
return is12Hour ? locale.meridian(date) : ''
}
const dateText = function (date) {
const dayName = locale.dow(date, true),
month = locale.month(date, true),
day = date.getDate()
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
return `${dayName} ${dayMonth}`
}
const drawDateTime = function (date) {
const t = settings.time
g.setColor(t.color)
g.setFont(t.font, t.size)
g.setFontAlign(0, 0) // centered
g.drawString(timeText(date), t.center, t.middle, true)
if (is12Hour && locale.hasMeridian) {
const a = settings.time.ampm
g.setColor(a.color)
g.setFont(a.font, a.size)
g.setFontAlign(1, -1) // right top
// at right edge of screen, aligned with time bottom
const left = screen.width - a.size * 2,
top = t.middle + t.size - a.size
g.drawString(ampmText(date), left, top, true)
}
const d = settings.date
g.setColor(d.color)
g.setFont(d.font, d.size)
g.setFontAlign(0, 0) // centered
g.drawString(dateText(date), d.center, d.middle, true)
}
const drawBar = function (date) {
const b = settings.bar
const seconds = date.getSeconds()
if (seconds === 0) {
// zero-size rect stills draws one line of pixels, we don't want that
return
}
const fraction = seconds / SECONDS_PER_MINUTE,
width = fraction * screen.width
g.setColor(b.color)
g.fillRect(0, b.top, width, b.top + b.thickness)
}
const clearScreen = function () {
g.setColor(0)
const timeTop = settings.time.middle - (settings.time.size * 4)
g.fillRect(0, timeTop, screen.width, screen.height)
}
let lastSeconds
const tick = function () {
g.reset()
const date = new Date()
const seconds = date.getSeconds()
if (lastSeconds > seconds) {
// new minute
clearScreen()
drawDateTime(date)
}
// the bar only gets larger, so drawing on top of the previous one is fine
drawBar(date)
lastSeconds = seconds
}
let iTick
const start = function () {
lastSeconds = 99 // force redraw
tick()
iTick = setInterval(tick, 1000)
}
const stop = function () {
if (iTick) {
clearInterval(iTick)
iTick = undefined
}
}
// clean app screen
g.clear()
Bangle.loadWidgets()
Bangle.drawWidgets()
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'})
Bangle.on('lcdPower', function (on) {
if (on) {
start()
} else {
stop()
}
})
start()
}

BIN
apps/barclock/clock-bar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

View File

@ -1,7 +0,0 @@
{
"name":"Binary Clock",
"type":"clock",
"icon":"*bclock",
"src":"-bclock"
}

View File

@ -33,6 +33,7 @@
<p>If ok, Click <button id="upload" class="btn btn-primary">Upload</button></p> <p>If ok, Click <button id="upload" class="btn btn-primary">Upload</button></p>
</div> </div>
<script src="../../lib/customize.js"></script>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://unpkg.com/osmtogeojson@2.2.12/osmtogeojson.js"></script> <script src="https://unpkg.com/osmtogeojson@2.2.12/osmtogeojson.js"></script>
@ -195,21 +196,12 @@ Bangle.on('mag', function(m) {
Bangle.setCompassPower(1); Bangle.setCompassPower(1);
Bangle.setGPSPower(1); Bangle.setGPSPower(1);
g.clear();`; g.clear();`;
var json = JSON.stringify({
name:"Beer Compass",
icon:"*beer",
src:"-beer"
});
var icon = `require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4"))`; var icon = `require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4"))`;
sendCustomizedApp({
window.postMessage({
id : "beer",
storage:[ storage:[
{name:"-beer", content:app}, {name:"beer.app.js", content:app},
{name:"+beer", content:json}, {name:"beer.img", content:icon, evaluate:true},
{name:"*beer", content:icon, evaluate:true},
] ]
}); });
}); });

View File

@ -1,6 +0,0 @@
{
"name":"Berlin Clock",
"type":"clock",
"icon":"*berlinc",
"src":"-berlinc"
}

View File

@ -1,7 +0,0 @@
{
"name": "BLE Scanner",
"type":"app",
"icon": "*blescan",
"src": "-blescan"
}

View File

@ -1,7 +0,0 @@
{
"name":"Large Clock",
"type":"clock",
"icon":"*blobclk",
"src":"-blobclk",
"sortorder":-10
}

View File

@ -1,7 +0,0 @@
{
"name":"Bold Clock",
"type":"clock",
"icon":"*boldclk",
"src":"-boldclk",
"sortorder":-10
}

View File

@ -1,2 +1,15 @@
0.02: Attempt to reset state of the interpreter better before loading an app 0.02: Attempt to reset state of the interpreter better before loading an app
0.03: Fix issue switching clockfaces via menu 0.03: Fix issue switching clockfaces via menu
0.04: Add alarm functionality
0.05: Add Welcome screen on boot
0.06: Disable GPS time log messages, add (default=1) setting to hide log messages
0.07: Fix issues with alarm scheduling
0.08: Fix issues if BLE=off, 'Make Connectable' is chosen, and the loader resets Bangle.js (fix #108)
0.09: Only check GPS for time after a fresh boot
0.10: Stop users calling save() (fix #125)
If Debug info is set to 'show' don't move to Terminal if connected!
0.11: Added vibrate as beep workaround
0.12: Add an event on BTN2 to open launcher when no clock detected (fix #147)
0.13: Now automatically load *.boot.js at startup
Move alarm code into alarm.boot.js
0.14: Move welcome loaders to *.boot.js

View File

@ -1,40 +1,45 @@
// This ALWAYS runs at boot // This ALWAYS runs at boot
E.setFlags({pretokenise:1}); E.setFlags({pretokenise:1});
// All of this is just shim for older Bangles // Load settings...
if (!Bangle.loadWidgets) { var s = require('Storage').readJSON('setting.json',1)||{};
Bangle.loadWidgets = function(){ if (s.ble!==false) {
global.WIDGETPOS={tl:32,tr:g.getWidth()-32,bl:32,br:g.getWidth()-32}; if (s.HID) { // Human interface device
global.WIDGETS={}; Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));
require("Storage").list().filter(a=>a[0]=='=').forEach(widget=>eval(require("Storage").read(widget))); NRF.setServices({}, {uart:true, hid:Bangle.HID});
};
Bangle.drawWidgets = function(){
for(var w of WIDGETS)w.draw()
};
Bangle.showLauncher = function(){
var l = require("Storage").list().filter(a=>a[0]=='+').map(app=>{
try { return require("Storage").readJSON(app); } catch (e) {}
}).find(app=>app.type=="launch");
if (l) load(l.src);
else E.showMessage("Launcher\nnot found");
};
var _load = load;
global.load = function(x) {
if (!x) _load(x);
else setTimeout(function(){
// attempt to remove any currently-running code
delete Bangle.buzz;
delete Bangle.beep;
Bangle.setLCDOffset&&Bangle.setLCDOffset(0);
Bangle.setLCDMode("direct");
g.clear();
clearInterval();
clearWatch();
Bangle.removeAllListeners();
NRF.removeAllListeners();
Bluetooth.removeAllListeners();
E.removeAllListeners();
for (var i in global) if (i!="g") delete global[i];
setTimeout('eval(require("Storage").read("'+x+'"));',20);
},20);
} }
} }
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
if (s.log) Terminal.setConsole(true); // if showing debug, force REPL onto terminal
else E.setConsole(null,{force:true}); // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
} else {
if (s.log && !NRF.getSecurityStatus().connected) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection)
else Bluetooth.setConsole(true); // else if no debug, force REPL to Bluetooth
}
// we just reset, so BLE should be on.
// Don't disconnect if something is already connected to us
if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep();
// Set time, vibrate, beep, etc
if (!s.vibrate) Bangle.buzz=Promise.resolve;
if (s.beep===false) Bangle.beep=Promise.resolve;
else if (s.beep=="vib") Bangle.beep = function (time, freq) {
return new Promise(function(resolve) {
if ((0|freq)<=0) freq=4000;
if ((0|time)<=0) time=200;
if (time>5000) time=5000;
analogWrite(D13,0.1,{freq:freq});
setTimeout(function() {
digitalWrite(D13,0);
resolve();
}, time);
});
};
Bangle.setLCDTimeout(s.timeout);
if (!s.timeout) Bangle.setLCDPower(1);
E.setTimeZone(s.timezone);
delete s;
// stop users doing bad things!
global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }
// Load *.boot.js files
require('Storage').list(/\.boot\.js/).map(bootFile=>{
eval(require('Storage').read(bootFile));
});

View File

@ -1,22 +1,38 @@
// This runs after a 'fresh' boot // This runs after a 'fresh' boot
var settings; var settings=require("Storage").readJSON('setting.json',1)||{};
try {
settings = require("Storage").readJSON('@setting');
} catch (e) {
settings = {}
}
// load clock if specified // load clock if specified
var clockApp = settings.clock; var clockApp = settings.clock;
if (clockApp) clockApp = require("Storage").read(clockApp) if (clockApp) clockApp = require("Storage").read(clockApp)
if (!clockApp) { if (!clockApp) {
var clockApps = require("Storage").list().filter(a=>a[0]=='+').map(app=>{ var clockApps = require("Storage").list(/\.info$/).map(app=>require("Storage").readJSON(app,1)||{}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
try { return require("Storage").readJSON(app); }
catch (e) {}
}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
if (clockApps && clockApps.length > 0) if (clockApps && clockApps.length > 0)
clockApp = require("Storage").read(clockApps[0].src); clockApp = require("Storage").read(clockApps[0].src);
delete clockApps; delete clockApps;
} }
if (clockApp) eval(clockApp); if (!clockApp) clockApp=`E.showMessage("No Clock Found");
else E.showMessage("No Clock Found"); setWatch(() => {
delete clockApp; Bangle.showLauncher();
}, BTN2, {repeat:false,edge:"falling"});)
`;
delete settings;
// check to see if our clock is wrong - if it is use GPS time
if ((new Date()).getFullYear()==1970) {
E.showMessage("Searching for\nGPS time");
Bangle.on('GPS',function cb(g) {
Bangle.setGPSPower(0);
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()==2250)) {
// GPS receiver's time not set - just boot clock anyway
eval(clockApp);delete clockApp;
return;
}
// We have a GPS time. Set time and reboot (to load alarms properly)
setTime(g.time.getTime()/1000);
load();
});
Bangle.setGPSPower(1);
} else {
eval(clockApp);
delete clockApp;
}

View File

@ -1,3 +0,0 @@
{
"name":"Bootloader","type":"boot"
}

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwglihGIxAWUwADBDCYTDhAXSFwQEGIxowBL4QXTx///AXWF6qnBwCTDO6EIF4KnEDwLWO/4QFx7FNdwQQEGwP4GBYUB/4QBDIYXMIgQAEDIIKCVwItJFggFEx4uKCAQUBX4QDC/B2KhASCAQP/AQQcDLpQlCLgQsCCoIGBC5IkCFon/xwxCDgIXJFwYxFHIR3ILwIkBCIeIFwQHBHgReIJAgCBOoP+MYZIHhB1EDgIRBA4ZIJC4LrEMYvoAgQXJxHvI4gtDC5OIF4QSDbYY3EC5QAKG4QXNPwg0BSBAJCIQhLCDwgXKIAwXUMo4XPFwrwKC4YOCUooVCR453DIxIXJU4IqDxwXJa45FDdgxnEC40IC4TbINQYXIRQZwDAAXv/xuBCwoXBVAgXDA4wXGSARcEC4o7BRwx4DOon+C4YiCLwxIDDAobDEYJGIGAYYBxDAD9AJDC5IwCDIYACJARGIDAapDaooWLDAZhEAoIWNMggADCqAAPA"))

73
apps/chrono/chrono.js Normal file
View File

@ -0,0 +1,73 @@
function msToTime(duration) {
var milliseconds = parseInt((duration % 1000) / 100),
seconds = Math.floor((duration / 1000) % 60),
minutes = Math.floor((duration / (1000 * 60)) % 60),
hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds;
}
var counter = 0;
var started = false;
function drawInterface() {
g.clear();
g.setFontAlign(0, 0);
g.setFont("6x8", 2);
g.drawString("+5m", g.getWidth() - 30, 30);
g.drawString("+30s", g.getWidth() - 30, g.getHeight() / 2);
g.drawString("+5s", g.getWidth() - 30, g.getHeight() - 30);
g.setFontAlign(0, 0); // center font
g.setFont("6x8", 3);
// draw the current counter value
g.drawString(msToTime(counter * 1000), g.getWidth() / 2 - 30, g.getHeight() / 2);
// optional - this keeps the watch LCD lit up
g.flip();
}
function countDown() {
if (counter > 0) {
if (started) {
counter--;
drawInterface();
}
} else {
if (started) {
Bangle.buzz();
}
}
}
setWatch((p) => {
if (p.time - p.lastTime < 0.1) {
counter = 0;
started = false;
} else {
counter += 60 * 5;
}
drawInterface();
}, BTN1, { repeat: true });
setWatch(() => {
counter += 30;
drawInterface();
}, BTN2, { repeat: true });
setWatch(() => {
counter += 5;
drawInterface();
}, BTN3, { repeat: true });
Bangle.on('touch', function (button) {
started = !started;
});
var interval = setInterval(countDown, 1000);
drawInterface();

BIN
apps/chrono/chrono.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1 +0,0 @@
0.02: Modified for use with new bootloader and firmware

View File

@ -1,6 +0,0 @@
{
"name":"Clock 3x2 Pix",
"type":"clock",
"icon":"*clck3x2",
"src":"-clck3x2"
}

View File

@ -1,5 +0,0 @@
{
"name":"Click Master",
"icon": "*clickms",
"src":"-clickms"
}

1
apps/cliock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.07: Submitted to App Loader

1
apps/cliock/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("kEgwkBiIA/ACBhLB6gqKB6g//B6I4DiDqCB40QB4MBAoIXDB40BAIIPNG44PLAoQvMB5RPEB5JvEBAav1f7wA/ABoA=="))

51
apps/cliock/app.js Normal file
View File

@ -0,0 +1,51 @@
var fontsize = 3;
var locale = require("locale");
var marginTop = 40;
var flag = false;
var WeekDays = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
function drawAll(){
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
updateTime();
updateRest(new Date());
}
function updateRest(now){
let date = locale.date(now,false);
writeLine(WeekDays[now.getDay()],1);
writeLine(date,2);
}
function updateTime(){
if (!Bangle.isLCDOn()) return;
let now = new Date();
let h = now.getHours();
let m = now.getMinutes();
h = h>=10?h:"0"+h;
m = m>=10?m:"0"+m;
writeLine(h+":"+m,0);
writeLine(flag?" ":"_",3);
flag = !flag;
if(now.getMinutes() == 0)
updateRest(now);
}
function writeLineStart(line){
g.drawString(">",4,marginTop+line*30);
}
function writeLine(str,line){
g.setFont("6x8",fontsize);
g.setColor(0,1,0);
g.setFontAlign(-1,-1);
g.clearRect(0,marginTop+line*30,((str.length+1)*20),marginTop+25+line*30);
writeLineStart(line);
g.drawString(str,25,marginTop+line*30);
}
drawAll();
Bangle.on('lcdPower',function(on) {
if (on)
drawAll();
});
var click = setInterval(updateTime, 1000);
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});

BIN
apps/cliock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

3
apps/clock2x3/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.02: Modified for use with new bootloader and firmware
0.03: Added 'reset' so we don't get the font color from widgets
0.04: Changed name from clck3x2 to clock2x3

View File

@ -57,9 +57,9 @@ const pixels = [[[0,0], // digit on/off pixels
let idTimeout = null; let idTimeout = null;
function drawTime() { function drawTime() {
g.clear(); g.clear(1);
Bangle.drawWidgets(); Bangle.drawWidgets();
g.reset();
let d = Date(); let d = Date();
let h = d.getHours(); let h = d.getHours();
let m = d.getMinutes(); let m = d.getMinutes();
@ -76,8 +76,7 @@ function drawTime() {
} }
let t = d.getSeconds()*1000 + d.getMilliseconds(); let t = d.getSeconds()*1000 + d.getMilliseconds();
let delta = (60000 - t) % 60000; // time till next minute idTimeout = setTimeout(drawTime, 60000 - t); // time till next minute
idTimeout = setTimeout(drawTime, delta);
} }
// special function to handle display switch on // special function to handle display switch on

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwgRC/AH4A/gED/k/5/wh/wgAFCBcg7NgAVBh/zDoYLkHaAFqAH4A/AH4AW")); require("heatshrink").decompress(atob("mEwgRC/AH4A/gED/k/5/wh/wgAFCBcg7NgAVBh/zDoYLkHaAFqAH4A/AH4AW"))

View File

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

View File

@ -1,5 +0,0 @@
{
"name":"Clock-Tris",
"icon":"*clotris",
"src":"-clotris"
}

View File

@ -1,5 +0,0 @@
{
"name":"Compass","type":"app",
"icon":"*compass",
"src":"-compass"
}

View File

@ -1,6 +0,0 @@
{
"name": "Centerclock",
"type": "clock",
"icon": "*ctrclk",
"src": "-ctrclk"
}

View File

@ -1,6 +0,0 @@
{
"name":"Cube",
"type":"app",
"icon":"*cube",
"src":"-cube"
}

3
apps/daysl/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: New Widget!
0.02: Improved calculation, new image for app
0.03: Improved display of number

1
apps/daysl/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgmIAH4A/AH4A/AEEAAAgGOC/4XLAgoGIDgYXTwEIBY4JEAw8YCIOAEY4+EAwwTCL44XNO5IX/C6i6LC8YABa5AXOF67vIwA5DAw5GDMhg7HjAXWIwQLFZIoGNC/4XKAH4A/AH4A/ADoA="))

67
apps/daysl/app.js Normal file
View File

@ -0,0 +1,67 @@
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
const storage = require('Storage');
let settings;
function updateSettings() {
storage.write('daysleft.json', settings);
}
function resetSettings() {
settings = {
day : 17,
month : 6,
year: 1981
};
updateSettings();
}
settings = storage.readJSON('daysleft.json',1);
if (!settings) resetSettings();
function showMenu() {
const datemenu = {
'': {
'title': 'Set Date',
'predraw': function() {
datemenu.Date.value = settings.day;
datemenu.Month.value = settings.month;
datemenu.Year.value = settings.year;
}
},
'Day': {
value: settings.day,
min: 1,
max: 31,
step: 1,
onchange: v => {
settings.day = v;
updateSettings();
}
},
'Month': {
value: settings.month,
min: 1,
max: 12,
step: 1,
onchange: v => {
settings.month = v;
updateSettings();
}
},
'Year': {
value: settings.year,
step: 1,
onchange: v => {
settings.year = v;
updateSettings();
}
}
};
datemenu['-Exit-'] = ()=>{load();};
return E.showMenu(datemenu);
}
showMenu();

BIN
apps/daysl/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

84
apps/daysl/widget.js Normal file
View File

@ -0,0 +1,84 @@
const storage = require('Storage');
let settings;
let height = 23;
let width = 34;
var debug = 0; //1 = show debug info
//write settings to file
function updateSettings() {
storage.write('daysleft.json', settings);
}
//Define standard settings
function resetSettings() {
settings = {
day : 17,
month : 6,
year: 2020
};
updateSettings();
}
settings = storage.readJSON('daysleft.json',1); //read storage
if (!settings) resetSettings(); //if settings file was not found, set to standard
var dd = settings.day,
mm = settings.month-1, //-1 because month is zero-based
yy = settings.year;
const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
const targetDate = new Date(yy, mm, dd); //is 00:00
const today = new Date(); //includes current time
const currentYear = today.getFullYear();
const currentMonth = today.getMonth();
const currentDay = today.getDate();
const todayMorning = new Date (currentYear, currentMonth, currentDay, 0, 0, 0); //create date object with today, but 00:00:00
const diffDays = (targetDate - todayMorning) / oneDay; //calculate day difference
function drawWidget() {
if (debug == 1) g.drawRect(this.x,this.y,this.x+width,this.y+height); //draw rectangle around widget area
g.reset();
//define font size and string position
//small if number has more than 3 digits (positive number)
if (diffDays >= 1000) {
g.setFont("6x8", 1);
g.drawString(diffDays,this.x+10,this.y+7);
}
//large if number has 3 digits (positive number)
if (diffDays <= 999 && diffDays >= 100) {
g.setFont("6x8", 2);
g.drawString(diffDays,this.x,this.y+4);
}
//large if number has 2 digits (positive number)
if (diffDays <= 99 && diffDays >= 10) {
g.setFont("6x8", 2);
g.drawString(diffDays,this.x+6,this.y+4);
}
//large if number has 1 digit (positive number)
if (diffDays <= 9 && diffDays >= 0) {
g.setFont("6x8", 2);
g.drawString(diffDays,this.x+13,this.y+4);
}
//large if number has 1 digit (negative number)
if (diffDays <= -1 && diffDays >= -9) {
g.setFont("6x8", 2);
g.drawString(diffDays,this.x+5,this.y+4);
}
//large if number has 2 digits (negative number)
if (diffDays <= -10 && diffDays >= -99) {
g.setFont("6x8", 2);
g.drawString(diffDays,this.x,this.y+4);
}
//large if number has 3 digits or more (negative number)
if (diffDays <= -100) {
g.setFont("6x8", 1);
g.drawString(diffDays,this.x,this.y+7);
}
}
//draw widget
WIDGETS["daysl"]={area:"tl",width:width,draw:drawWidget};

9
apps/dclock/ChangeLog Normal file
View File

@ -0,0 +1,9 @@
0.01: branched from simple clock and added seconds
0.02: add timestamp (tst)
0.03: fix timestamp round to whole number
0.04: add iso datetime and move day of the week (d) / month names (m)
0.05: add beats (@)
0.06: tidy up
0.07: add days in current month (md) and days since new moon (l)
0.08: update icon
0.09: Use localised month and day of the week from locale

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEIf4A5/8wgf/AwUB/8gh/zA4QMCl/xA4cAichgIaBiEDgMgmECDQMAkMA+EgiYvDkQJBkcQgMQDwMggUiiECG4MikEBmQWCgURiEREQIXBCIMxkIIBAoMSiQ4BGoIABKgPykRSBI4JfC+c/iARBl8zmBfEAAUvIgIAUkbAtgalB+ADDBIKSBHgUgmYJCAAa6BmCoBAYMiBIMRC4UQmEAAoQvFmUDAYUSmcxWIKMBEQKrBOw0yh8wmcyj4nBIYQDB+cwBAQA/ABUxgUDkBqBgchkMiiUikMRgSOBkR3BkEhC4MgiQHBiADBC4UQAYMRiUxkECAAITBC4MSiUQF4MTiQTBBAIDBkcCiMxkUTAYIvCAH4A/AH4AKiIPPgMxiESgUQgECgMBdAMiiUgC48ikUBiEBiIXDGQURiIbBF48RkAvCEwIvCkERgQMBRHpDBOoRhBNoJOBJIkiKYMjgcTOoMhLQMQmMDDIMjQQInEC4MhiUSkQHCC4MAkAXCiUjiZ5UiR5jLwLaBAQJ1BAgIAMCgMxMwMgkciAoMjC5pqBRwPxCoMiiUyGBsgiBBBiESVAKzBf+YACA=="))

112
apps/dclock/clock-dev.js Normal file
View File

@ -0,0 +1,112 @@
var locale = require("locale");
/* jshint esversion: 6 */
const timeFontSize = 4;
const dateFontSize = 3;
const smallFontSize = 2;
const font = "6x8";
const xyCenter = g.getWidth() / 2;
const yposTime = 50;
const yposDate = 85;
const yposTst = 115;
const yposDml = 170;
const yposDayMonth = 195;
const yposGMT = 220;
// Check settings for what type our clock should be
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
function getUTCTime(d) {
return d.toUTCString().split(' ')[4].split(':').map(function(d){return Number(d)});
}
function drawSimpleClock() {
// get date
var d = new Date();
var da = d.toString().split(" ");
var dutc = getUTCTime(d);
g.reset(); // default draw styles
// drawSting centered
g.setFontAlign(0, 0);
// draw time
var time = da[4].split(":");
var hours = time[0],
minutes = time[1],
seconds = time[2];
var meridian = "";
if (is12Hour) {
hours = parseInt(hours,10);
meridian = "AM";
if (hours == 0) {
hours = 12;
meridian = "AM";
} else if (hours >= 12) {
meridian = "PM";
if (hours>12) hours -= 12;
}
hours = (" "+hours).substr(-2);
}
// Time
g.setFont(font, timeFontSize);
g.drawString(`${hours}:${minutes}:${seconds}`, xyCenter, yposTime, true);
g.setFont(font, smallFontSize);
g.drawString(meridian, xyCenter + 102, yposTime + 10, true);
// Date String
g.setFont(font, dateFontSize);
g.drawString(`${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}`, xyCenter, yposDate, true);
// Timestamp
var tst = Math.round(d.getTime());
g.setFont(font, smallFontSize);
g.drawString(`tst:${tst}`, xyCenter, yposTst, true);
//Days in month
var dom = new Date(d.getFullYear(), d.getMonth()+1, 0).getDate();
//Days since full moon
var knownnew = new Date(2020,02,24,09,28,0);
// Get millisecond difference and divide down to cycles
var cycles = (d.getTime()-knownnew.getTime())/1000/60/60/24/29.53;
// Multiply decimal component back into days since new moon
var sincenew = (cycles % 1)*29.53;
// Draw days in month and sime since new moon
g.setFont(font, smallFontSize);
g.drawString(`md:${dom} l:${sincenew.toFixed(2)}`, xyCenter, yposDml, true);
// draw Month name, Day of the week and beats
var beats = Math.floor((((dutc[0] + 1) % 24) + dutc[1] / 60 + dutc[2] / 3600) * 1000 / 24);
g.setFont(font, smallFontSize);
g.drawString(`m:${locale.month(d,true)} d:${locale.dow(d,true)} @${beats}`, xyCenter, yposDayMonth, true);
// draw gmt
var gmt = da[5];
g.setFont(font, smallFontSize);
g.drawString(gmt, xyCenter, yposGMT, true);
}
// handle switch display on by pressing BTN1
Bangle.on('lcdPower', function(on) {
if (on) drawSimpleClock();
});
// clean app screen
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
// refesh every 100 milliseconds
setInterval(drawSimpleClock, 100);
// draw now
drawSimpleClock();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});

BIN
apps/dclock/clock-dev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -9,6 +9,7 @@ var scenes = [
y+=step; y+=step;
if (y>70) { if (y>70) {
clearInterval(i); clearInterval(i);
i = undefined; i = undefined;
} }
g.clearRect(0,y-(step+1),240,y-1); g.clearRect(0,y-(step+1),240,y-1);
@ -159,10 +160,13 @@ var scenes = [
]; ];
var sceneNo = scenes.length-1; var sceneNo = scenes.length-1;
var stop = scenes[sceneNo](); var stop;
setInterval(function() { function next() {
sceneNo++; sceneNo++;
if (sceneNo>=scenes.length) sceneNo=0; if (sceneNo>=scenes.length) sceneNo=0;
stop(); if (stop) stop();
clearInterval();
stop = scenes[sceneNo](); stop = scenes[sceneNo]();
}, 10000); setTimeout(next, 10000);
}
next()

View File

@ -1,5 +0,0 @@
{
"name":"Demo Loop",
"icon":"*demoapp",
"src":"-demoapp"
}

1
apps/dotclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Based on the Analog Clock app, minimal dot interface

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBIf4A/AGUJyAXtACeZBCAOJh/wC6IADC4gA/XEINJC64A/AHcP+ACD/4CBTB0Ph8A+ACBAIKoKC65HKC4gA/AAfACysM5gvjTBgNKC64A/AEWZBCAXdADa4XaH4A/AAgA=="))

162
apps/dotclock/clock-dot.js Normal file
View File

@ -0,0 +1,162 @@
let g;
let Bangle;
const locale = require('locale');
const p = Math.PI / 2;
const pRad = Math.PI / 180;
const faceWidth = 100; // watch face radius
let timer = null;
let currentDate = new Date();
let hourRadius = 60;
let minRadius = 80;
const centerPx = g.getWidth() / 2;
const seconds = (angle) => {
const a = angle * pRad;
const x = centerPx + Math.sin(a) * faceWidth;
const y = centerPx - Math.cos(a) * faceWidth;
// if 15 degrees, make hour marker larger
const radius = (angle % 15) ? 2 : 4;
g.fillCircle(x, y, radius);
};
const hourDot = (angle,radius) => {
const a = angle * pRad;
const x = centerPx + Math.sin(a) * hourRadius;
const y = centerPx - Math.cos(a) * hourRadius;
g.fillCircle(x, y, radius);
};
const minDot = (angle,radius) => {
const a = angle * pRad;
const x = centerPx + Math.sin(a) * minRadius;
const y = centerPx - Math.cos(a) * minRadius;
g.fillCircle(x, y, radius);
};
const drawAll = () => {
g.clear();
currentDate = new Date();
// draw hands first
onMinute();
// draw seconds
const currentSec = currentDate.getSeconds();
// draw all secs
for (let i = 0; i < 60; i++) {
if (i > currentSec) {
g.setColor(0, 0, 0.6);
} else {
g.setColor(0.3, 0.3, 1);
}
seconds((360 * i) / 60);
}
onSecond();
};
const resetSeconds = () => {
g.setColor(0, 0, 0.6);
for (let i = 0; i < 60; i++) {
seconds((360 * i) / 60);
}
};
const drawMin = () => {
g.setColor(0.5, 0.5, 0.5);
for (let i = 0; i < 60; i++) {
minDot((360 * i) / 60,1);
}
};
const drawHour = () => {
g.setColor(0.5, 0.5, 0.5);
for (let i = 0; i < 12; i++) {
hourDot((360 * 5 * i) / 60,1);
}
};
const onSecond = () => {
g.setColor(0.3, 0.3, 1);
seconds((360 * currentDate.getSeconds()) / 60);
if (currentDate.getSeconds() === 59) {
resetSeconds();
onMinute();
}
g.setColor(1, 0.7, 0.2);
currentDate = new Date();
seconds((360 * currentDate.getSeconds()) / 60);
g.setColor(1, 1, 1);
};
const drawDate = () => {
g.reset();
g.setColor(1, 1, 1);
g.setFont('6x8', 2);
const dayString = locale.dow(currentDate, true);
// pad left date
const dateString = ((currentDate.getDate() < 10) ? '0' : '') + currentDate.getDate().toString();
const dateDisplay = `${dayString} ${dateString}`;
// console.log(`${dayString}|${dateString}`);
// center date
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
const t = centerPx - 6 ;
g.drawString(dateDisplay, l, t);
// console.log(l, t);
};
const onMinute = () => {
if (currentDate.getHours() === 0 && currentDate.getMinutes() === 0) {
g.clear();
resetSeconds();
}
// clear existing hands
g.setColor(0, 0, 0);
hourDot((360 * currentDate.getHours()) / 12,4);
minDot((360 * currentDate.getMinutes()) / 60,3);
// Hour
drawHour();
// Minute
drawMin();
// get new date, then draw new hands
currentDate = new Date();
g.setColor(1, 0, 0);
// Hour
hourDot((360 * currentDate.getHours()) / 12,4);
g.setColor(1, 0.9, 0.9);
// Minute
minDot((360 * currentDate.getMinutes()) / 60,3);
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
Bangle.buzz();
}
drawDate();
};
const startTimers = () => {
timer = setInterval(onSecond, 1000);
};
Bangle.on('lcdPower', (on) => {
if (on) {
// g.clear();
drawAll();
startTimers();
Bangle.drawWidgets();
} else {
if (timer) {
clearInterval(timer);
}
}
});
g.clear();
resetSeconds();
startTimers();
drawAll();
Bangle.loadWidgets();
Bangle.drawWidgets();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

BIN
apps/dotclock/clock-dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -31,7 +31,7 @@ function showMainMenu() {
} }
function eraseApp(app) { function eraseApp(app) {
E.showMessage('Erasing ' + app.name + '...'); E.showMessage('Erasing\n' + app.name + '...');
storage.erase(app['']); storage.erase(app['']);
storage.erase(app.icon); storage.erase(app.icon);
storage.erase(app.src); storage.erase(app.src);
@ -44,7 +44,7 @@ function showAppMenu(app) {
}, },
'< Back': () => m = showApps(), '< Back': () => m = showApps(),
'Erase': () => { 'Erase': () => {
E.showPrompt('Erase ' + app.name + '?').then((v) => { E.showPrompt('Erase\n' + app.name + '?').then((v) => {
if (v) { if (v) {
Bangle.buzz(100, 1); Bangle.buzz(100, 1);
eraseApp(app); eraseApp(app);
@ -66,10 +66,10 @@ function showApps() {
'< Back': () => m = showMainMenu(), '< Back': () => m = showMainMenu(),
}; };
var list = storage.list().filter((a)=> { var list = storage.list(/\.info$/).filter((a)=> {
return a[0]=='+' && a !== '+setting'; return a !== 'setting.info';
}).sort().map((app) => { }).sort().map((app) => {
var ret = storage.readJSON(app); var ret = storage.readJSON(app,1)||{};
ret[''] = app; ret[''] = app;
return ret; return ret;
}); });

View File

@ -1,5 +0,0 @@
{
"name":"App Manager","type":"app",
"icon":"*files",
"src":"-files"
}

Some files were not shown because too many files have changed in this diff Show More