Merge branch 'master' into master

pull/240/head
Gordon Williams 2020-04-07 08:38:06 +01:00 committed by GitHub
commit 416e749b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1818 additions and 213 deletions

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

View File

@ -6,6 +6,10 @@ Bangle.js App Loader (and Apps)
* 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?
* A list of apps is in `apps.json`
@ -32,6 +36,7 @@ easily distinguish between file types, we use the following:
* `stuff.img` is an image
* `stuff.app.js` is JS code for applications
* `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
@ -314,6 +319,48 @@ 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
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"

View File

@ -27,7 +27,7 @@
{ "id": "daysl",
"name": "Days left",
"icon": "app.png",
"version":"0.02",
"version":"0.03",
"description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.",
"tags": "",
"allow_emulator":false,
@ -78,24 +78,26 @@
{ "id": "welcome",
"name": "Welcome",
"icon": "app.png",
"version":"0.04",
"version":"0.05",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator":true,
"storage": [
{"name":"welcome.js","url":"welcome.js"},
{"name":"welcome.app.js","url":"app.js"},
{"name":"welcome.settings.js","url":"settings.js"},
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "gbridge",
"name": "Gadgetbridge",
"icon": "app.png",
"version":"0.06",
"version":"0.07",
"description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget",
"type":"widget",
"storage": [
{"name":"gbridge.app.js","url":"app.js"},
{"name":"gbridge.settings.js","url":"settings.js"},
{"name":"gbridge.img","url":"app-icon.js","evaluate":true},
{"name":"gbridge.wid.js","url":"widget.js"}
]
@ -117,11 +119,12 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
"version":"0.08",
"version":"0.10",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"storage": [
{"name":"setting.app.js","url":"settings.js"},
{"name":"setting.boot.js","url":"boot.js"},
{"name":"setting.json","url":"settings-default.json","evaluate":true},
{"name":"setting.img","url":"settings-icon.js","evaluate":true}
],
@ -159,7 +162,7 @@
{ "id": "aclock",
"name": "Analog Clock",
"icon": "clock-analog.png",
"version":"0.10",
"version": "0.11",
"description": "An Analog Clock",
"tags": "clock",
"type":"clock",
@ -336,13 +339,16 @@
},
{ "id": "widbatpc",
"name": "Battery Level Widget (with percentage)",
"shortName": "Battery Widget",
"icon": "widget.png",
"version":"0.06",
"version":"0.08",
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
"tags": "widget,battery",
"type":"widget",
"storage": [
{"name":"widbatpc.wid.js","url":"widget.js"}
{"name":"widbatpc.wid.js","url":"widget.js"},
{"name":"widbatpc.settings.js","url":"settings.js"},
{"name":"widbatpc.settings.json","content": "{}"}
]
},
{ "id": "widbt",
@ -864,6 +870,19 @@
{"name":"torch.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "wohrm",
"name": "Workout HRM",
"icon": "app.png",
"version":"0.06",
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
"tags": "hrm,workout",
"type": "app",
"allow_emulator":true,
"storage": [
{"name":"wohrm.app.js","url":"app.js"},
{"name":"wohrm.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "widid",
"name": "Bluetooth ID Widget",
"icon": "widget.png",
@ -1014,7 +1033,7 @@
"name": "Touch Launcher",
"shortName":"Menu",
"icon": "app.png",
"version":"0.02",
"version":"0.04",
"description": "Touch enable left to right launcher.",
"tags": "tool,system,launcher",
"type":"launch",
@ -1060,5 +1079,18 @@
"storage": [
{"name":"widmp.wid.js","url":"widget.js"}
]
},
{ "id": "minionclk",
"name": "Minion clock",
"icon": "minionclk.png",
"version": "0.01",
"description": "Minion themed clock.",
"tags": "clock,minion",
"type": "clock",
"allow_emulator": true,
"storage": [
{"name":"minionclk.app.js","url":"app.js"},
{"name":"minionclk.img","url":"app-icon.js","evaluate":true}
]
}
]

View File

@ -5,3 +5,4 @@
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,3 +1,4 @@
// eliminate ide undefined errors
let g;
let Bangle;
@ -5,15 +6,18 @@ let Bangle;
const locale = require('locale');
const p = Math.PI / 2;
const pRad = Math.PI / 180;
const faceWidth = 100; // watch face radius
const faceWidth = 100; // watch face radius (240/2 - 24px for widget area)
const widgetHeight=24+1;
let timer = null;
let currentDate = new Date();
const centerPx = g.getWidth() / 2;
const centerX = g.getWidth() / 2;
const centerY = (g.getWidth() / 2) + widgetHeight/2;
const seconds = (angle) => {
const a = angle * pRad;
const x = centerPx + Math.sin(a) * faceWidth;
const y = centerPx - Math.cos(a) * faceWidth;
const x = centerX + Math.sin(a) * faceWidth;
const y = centerY - Math.cos(a) * faceWidth;
// if 15 degrees, make hour marker larger
const radius = (angle % 15) ? 2 : 4;
@ -25,14 +29,14 @@ const hand = (angle, r1, r2) => {
const r3 = 3;
g.fillPoly([
Math.round(centerPx + Math.sin(a) * r1),
Math.round(centerPx - Math.cos(a) * r1),
Math.round(centerPx + Math.sin(a + p) * r3),
Math.round(centerPx - Math.cos(a + p) * r3),
Math.round(centerPx + Math.sin(a) * r2),
Math.round(centerPx - Math.cos(a) * r2),
Math.round(centerPx + Math.sin(a - p) * r3),
Math.round(centerPx - Math.cos(a - p) * r3)
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)
]);
};
@ -54,6 +58,7 @@ const drawAll = () => {
seconds((360 * i) / 60);
}
onSecond();
};
const resetSeconds = () => {
@ -88,8 +93,8 @@ const drawDate = () => {
// console.log(`${dayString}|${dateString}`);
// center date
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
const t = centerPx + 37;
g.drawString(dateDisplay, l, t);
const t = centerY + 37;
g.drawString(dateDisplay, l, t, true);
// console.log(l, t);
};
const onMinute = () => {

View File

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

View File

@ -1,39 +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);
}
function resetSettings() {
settings = {
day : 17,
month : 6,
year: 2020
};
updateSettings();
//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);
}
}
settings = storage.readJSON('daysleft.json',1);
if (!settings) resetSettings();
var dd = settings.day,
mm = settings.month-1, //month is zero-based
yy = settings.year;
const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
const targetDate = new Date(yy, mm, dd);
const today = new Date();
//create date object with today, but 00:00:00
const currentYear = today.getFullYear();
const currentMonth = today.getMonth();
const currentDay = today.getDate();
const todayMorning = new Date (currentYear, currentMonth, currentDay, 0, 0, 0);
const diffDays = (targetDate - todayMorning) / oneDay;
WIDGETS["daysl"]={area:"tl",width:40,draw:function(){
g.setFont("6x8", 1);
g.drawString(diffDays,this.x+12,this.y+12);
}};
//draw widget
WIDGETS["daysl"]={area:"tl",width:width,draw:drawWidget};

View File

@ -5,3 +5,4 @@
0.05: Show incoming call notification
Optimize animation, limit title length
0.06: Gadgetbridge App 'Connected' state is no longer toggleable
0.07: Move configuration to settings menu

View File

@ -1,19 +0,0 @@
function gb(j) {
Bluetooth.println(JSON.stringify(j));
}
var mainmenu = {
"" : { "title" : "Gadgetbridge" },
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
"Find Phone" : function() { E.showMenu(findPhone); },
"Exit" : ()=> {load();},
};
var findPhone = {
"" : { "title" : "-- Find Phone --" },
"On" : _=>gb({t:"findPhone",n:true}),
"Off" : _=>gb({t:"findPhone",n:false}),
"< Back" : function() { E.showMenu(mainmenu); },
};
E.showMenu(mainmenu);

21
apps/gbridge/settings.js Normal file
View File

@ -0,0 +1,21 @@
(function(back) {
function gb(j) {
Bluetooth.println(JSON.stringify(j));
}
var mainmenu = {
"" : { "title" : "Gadgetbridge" },
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
"Find Phone" : function() { E.showMenu(findPhone); },
"< Back" : back,
};
var findPhone = {
"" : { "title" : "-- Find Phone --" },
"On" : _=>gb({t:"findPhone",n:true}),
"Off" : _=>gb({t:"findPhone",n:false}),
"< Back" : function() { E.showMenu(mainmenu); },
};
E.showMenu(mainmenu);
})

1
apps/minionclk/ChangeLog Executable file
View File

@ -0,0 +1 @@
0.01: First release

1
apps/minionclk/app-icon.js Executable file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwhAaXlZEolVVvOj0mq1XOv9/qtWFb8rquj1XV5wBDAA2jvMqNLMqwAsCABBhBAIujqpbWvIsCLowBHMg0qFyVVFQJNDK4YoEAYxjFvIuQwHP6ur5+rDgfU5wBDMI2qCYOsC4XV0bFOwIWBAAeBAIOrMYYsF54sCCIWswQDB52AGBcrFIOtF4gAEMoJfDAAOrCYQuDAIowKFwQWIBIeA52pGQPWLYgAFA4YDBGA4uDEwQYFGQvPL4IuKC4wwHFglWAIQYIYoQuFCYwDGqrqFF4YYCYBKeHHwgRLlZhCLowMBKIIubWAtWF4PXBQRdEBAIBHGAoTRCIRfCDQQvBLofXB4NVBIQcDFweAOYdWp9WqwrDGA8ABoRJFAAOswFOqtUwBNFeQYVCwMqp4ABqwbDCowvCAgKOGf4N5aAIwKKIVPpwRBGQOBFw5fCIgZfGwFVlT/BqtVBQQwFUAVWFwMAlYvCL6mBqkqDgNUF4RLGAgVPFwMAklPwD0GX5gOCXoReBJgSvDIwWrAoN6py/Cp58DCYxQBVIYwHqyQBbgL6EX4qQDFwRdFLAifBaoQaEAJIuDCYWrEgoTIGAerWIJfHGRZdECZ5fD0bQBIwJgEIoynEGBJxIAAYPBwHNlUq6owBMIZ4OMKQPC0eiqsr53PX4guOwBhOComk0ejqpfB53OJZAeDU4lVvN5OQQXKBIeA0RfBvNV5wwBMI4uD1oLDFoN4AQJEMBYWl0fOL4NUL4QwCPw4BEwN4AAejvGACZaMBLoRfGAIWr1Z8HwGkFQRIBDgekwAVGFoOAB4QTDqgvBFwQDD1Wk1el1YsBv4oDAIoAB0d/GQOlAIV/CpF51ZfFAIgAEUoOiAJYmEAAoHDvOBFxWqAIWpFxwnDOJABBquJ1QwM5oDCMYYDDAIN/5+r6CABHRKOBmVewIwCYY4sC0bGB678B1ekZYIAB0ulwOlvWkIQLJCMY0yq2sr2sMJYABp96vQnB0tPz4FBBANzAAWj5pdI0dWq0zr2Ir2rMJKQCvNIp9PudIuYDBAIV0FwSMFL4d/LoMzL4WBwIwD6hhH5ujuZXBFAIDDAIOdFwPNL4hdCwBdCq15AgMrAQLDB52pRYYACBIWjvGdK4NJAQOdvIlB5oXB0QwBLoWjqsrAAaSCGANVGAJHBSQjBEAAINBewIDCLYIBCAAJfDv5dCLAIvBvIEDAAJoBwGqDQSUBY4htDBwIBBAoIwDL4WjvNdhAvCAAYFGhDGCY4IAB1QABFwQwDv4BB1V/0eA0mALINdP4IpGMYcAAIQABGAIxDAAIFBruCroAGq1eFALkDmdeD4IjDGQYCCBQYDBCgIqBAJAoBAQIDCmeBCoIDCGgIfBLooADL4YBCJAIiCAANdAIQoCAI4ABAYdWKQQDDMooEBlVPBwJfCGAwABFxABDSAZYGLo1OvFOIgQaBLYZhFAIsyFgYAEFAUqpxUBFYQ="))

68
apps/minionclk/app.js Executable file
View File

@ -0,0 +1,68 @@
const bob = require("heatshrink").decompress(atob("nk8hAaXlYLWAEsqvN/0gBBql5lQ2tquj1XV5wBJ52j0hACPsdP1QsBAQQAGBIIBF51/P8OkN5R1GIxF5HLmAFgoDLPZfOpzmZ6vPFwomCPaA6DAYOjeq2A1YyCdI4HGQJQ8F1T2SJ4Oq1XW1es1mtAQOrPoPUAIh3J54ZHIAR5S62s64cBwIBGQIOqHQK4HKQYVDAAIFC1g+BHh9VHAQAFDwQDDHoJ5E54BB6AaBKQ5YGqo6MwJzGHQ4BDeIj/BR4JxDABY8BvI6OOYgaEHwZADHgQ6BZA42GAIusPJNW64eFqzJDlcrERA8BHQI2FqwaBDYYGBPI45GCoIgCLoVWQ5NWXA2rKhaiGLAwOGEAmADxJPDVA51ElQaMC4ouEWALdEHRg8Dc4woCDJo8EAIYxCHQQIFHiwaRegJ5EcYWsHgbrKbBA8GDSrNDO4wfRKgR3FDSh3CN4UrdwZbSHYZ5DHajMFHYQGCHalWO4jtQDQwABwAGCAAQfTKoK0EHahwCeARdFHakASIZWVZ4Q8CO4YgWO4QbCO6hWGEIKYZKzZ3DLog7UG4I6C1lWDSdWO4bpCO4bwUwKYEHajMDwOAlUkLojTUd4gaTZoRWC0YIB1eJLqo4EWiqRE0mjlcr1QkEeKFWOooBCHiB2CC4WA5wzB52rEQgfPHQwABAYJXOHQ2iO4XO6omFEJh1BEAgBGPJlWDIQbC0ej50qgHV1XPEwohKcwRbEvJ3EBQTrLFomkOwOjlR3C5w8GMAR8ClYuBLIgOCvN4HgIZFDQYbBlaOBR4YNCwA5B0XOpzvBHgWqTw4AFxB1EvQ6BAAI8GDZILEdgQBCqp3DPIRfIEQwABvJ1CvGkvGiwA6IAYoBCv6wCAAVOlQ6DAIWkL5ABEwN40Z1CAYJfCv7zEAJNWOYZ3KAIWq5yYHFYLOBLIrVCAIh7BOIzpECoYDDpw7BHAQDG1WqwGkAIN/CwIABLY4LDAAZ9BwAABvLCBC5IBBvOrO44BEAAmjAIPN0XOAJyHIAAgPEquBHBi4BAId+HwWiHxqJDRpYBDq2I1R3LIAQ6BAAOpPogABGgIDDOomk0nP5pIGd42Aq1ewI8CeZI6CHAJhCEAIDCdIo2B0er1esAQIZBC4Z9JlVW1leeKGp0es5+s6+s6ABB0oFBAIervWr0ulub7OqtWmdexJ4BGxB5G0V6pF6wItB0t6p9PvQABvINBudJudPzwXCHQwBDlY6BO4WIwPPPJbvDvA0BFgNzAAIDGugGCzrtHdYh1DAIOBrzxBPIgAIeIXNHgoBCGwYADzoVB0fNOpMyHQdW5+Bro7BHgQAB6jxJAAOjOYhxCAIukOoPN5ujdZFWqyyD0d6AwUrquBwAuB1I1FHgRJBMoQ9BWg1zzw0BDYI6B0R3DAAJ1BvMyp8rAAV4IQWBIodewAeCHAZ5IAAJoBAAXHHAJWDO4TtEdYQvEHgejAwIKClcqIQRdDXYbzFeoQBIGwIDDOot/VgQ6FAAIGBlgBCAAMzmZPBF4LzDACB1FAAOi1WjvFVr0zGYQ7GAAMAAYpPBwNeAIOIwOrfYOA1eA1WkAIWjAIekv4PBwCVBruBq5eBEYIABlcBF4wCEHw55CHwQABIQQBBABkzAILlCHQR1CFYavEPgsAAAIDEDQNdAwQAaHQNWEwQ0DHAh3KleBLoI7dHQKuFWQo0EAIsISoKdBHbyyHNgwADlVVpwEBDANWro7fd4Q6HO495vF5QgIYCd75eBHYUINAN5lQ3EA"));
const locale = require("locale");
const black = 0x0000;
const white = 0xFFFF;
let hour;
let minute;
let date;
function draw() {
const d = new Date();
const newHour = ('0' + d.getHours()).substr(-2);
const newMinute = ('0' + d.getMinutes()).substr(-2);
const newDate = locale.date(d).trim();
g.setFontAlign(0, 0, 0);
if (newHour !== hour) {
g.setFontVector(48);
g.setColor(black);
g.drawString(hour, 64, 92);
g.setColor(white);
g.drawString(newHour, 64, 92);
hour = newHour;
}
if (newMinute !== minute) {
g.setFontVector(48);
g.setColor(black);
g.drawString(minute, 172, 92);
g.setColor(white);
g.drawString(newMinute, 172, 92);
minute = newMinute;
}
if (newDate !== date) {
g.setFontVector(12);
g.setColor(black);
g.drawString(date, 120, 228);
g.setColor(0xFFFF);
g.drawString(newDate, 120, 228);
date = newDate;
}
}
function drawAll() {
hour = '';
minute = '';
date = '';
g.drawImage(bob, 0, 0, { scale: 4 });
draw();
}
Bangle.on('lcdPower', function(on) {
if (on) {
drawAll();
}
});
Bangle.loadWidgets();
Bangle.drawWidgets();
setInterval(draw, 1000);
drawAll();
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});

BIN
apps/minionclk/minionclk.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -5,3 +5,6 @@
0.06: Remove distance setting as there's a separate app for Locale now
0.07: Added vibrate as beep workaround
0.08: Add support for app/widget settings
0.09: Move Welcome into App/widget settings
0.10: Added LCD wake-up settings
Adds LCD brightness setting

6
apps/setting/boot.js Normal file
View File

@ -0,0 +1,6 @@
(() => {
var settings = require('Storage').readJSON('setting.json', true);
if (settings != undefined) {
Bangle.setOptions(settings.options);
}
})()

View File

@ -10,4 +10,16 @@
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
// welcomed : undefined/true (whether welcome app should show)
brightness: 1, // LCD brightness from 0 to 1
options: {
wakeOnBTN1: true,
wakeOnBTN2: true,
wakeOnBTN3: true,
wakeOnFaceUp: false,
wakeOnTouch: false,
wakeOnTwist: true,
twistThreshold: 819.2,
twistMaxY: -800,
twistTimeout: 1000
}
}

View File

@ -9,6 +9,21 @@ function updateSettings() {
storage.write('setting.json', settings);
}
function updateOptions() {
updateSettings();
Bangle.setOptions(settings.options)
}
function gToInternal(g) {
// converts g to Espruino internal unit
return g * 8192;
}
function internalToG(u) {
// converts Espruino internal unit to g
return u / 8192
}
function resetSettings() {
settings = {
ble: true, // Bluetooth enabled by default
@ -18,22 +33,34 @@ function resetSettings() {
vibrate: true, // Vibration enabled by default. App must support
beep: "vib", // Beep enabled by default. App must support
timezone: 0, // Set the timezone for the device
HID : false, // BLE HID mode, off by default
HID: false, // BLE HID mode, off by default
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
brightness: 1, // LCD brightness from 0 to 1
// welcomed : undefined/true (whether welcome app should show)
options: {
wakeOnBTN1: true,
wakeOnBTN2: true,
wakeOnBTN3: true,
wakeOnFaceUp: false,
wakeOnTouch: false,
wakeOnTwist: true,
twistThreshold: 819.2,
twistMaxY: -800,
twistTimeout: 1000
}
};
updateSettings();
}
settings = storage.readJSON('setting.json',1);
settings = storage.readJSON('setting.json', 1);
if (!settings) resetSettings();
const boolFormat = v => v ? "On" : "Off";
function showMainMenu() {
var beepV = [ false,true,"vib" ];
var beepN = [ "Off","Piezo","Vibrate" ];
var beepV = [false, true, "vib"];
var beepN = ["Off", "Piezo", "Vibrate"];
const mainmenu = {
'': { 'title': 'Settings' },
'Make Connectable': makeConnectable,
@ -72,14 +99,25 @@ function showMainMenu() {
Bangle.setLCDTimeout(settings.timeout);
}
},
'LCD Brightness': {
value: settings.brightness,
min: 0,
max: 1,
step: 0.1,
onchange: v => {
settings.brightness = v || 1;
updateSettings();
Bangle.setLCDBrightness(settings.brightness);
}
},
'Beep': {
value: 0|beepV.indexOf(settings.beep),
min:0,max:2,
format: v=>beepN[v],
value: 0 | beepV.indexOf(settings.beep),
min: 0, max: 2,
format: v => beepN[v],
onchange: v => {
settings.beep = beepV[v];
if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200) } // piezo
else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200) } // vibrate
if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo
else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200); } // vibrate
updateSettings();
}
},
@ -91,18 +129,10 @@ function showMainMenu() {
updateSettings();
if (settings.vibrate) {
VIBRATE.write(1);
setTimeout(()=>VIBRATE.write(0), 10);
setTimeout(() => VIBRATE.write(0), 10);
}
}
},
'Welcome App': {
value: !settings.welcomed,
format: boolFormat,
onchange: v => {
settings.welcomed = v?undefined:true;
updateSettings();
}
},
'Locale': showLocaleMenu,
'Select Clock': showClockMenu,
'HID': {
@ -114,14 +144,101 @@ function showMainMenu() {
}
},
'Set Time': showSetTimeMenu,
'LCD Wake-Up': showWakeUpMenu,
'App/widget settings': showAppSettingsMenu,
'Reset Settings': showResetMenu,
'Turn Off': Bangle.off,
'< Back': ()=> {load();}
'< Back': () => { load(); }
};
return E.showMenu(mainmenu);
}
function showWakeUpMenu() {
const wakeUpMenu = {
'': { 'title': 'LCD Wake-Up' },
'< Back': showMainMenu,
'Wake On BTN1': {
value: settings.options.wakeOnBTN1,
format: boolFormat,
onchange: () => {
settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1;
updateOptions();
}
},
'Wake On BTN2': {
value: settings.options.wakeOnBTN2,
format: boolFormat,
onchange: () => {
settings.options.wakeOnBTN2 = !settings.options.wakeOnBTN2;
updateOptions();
}
},
'Wake On BTN3': {
value: settings.options.wakeOnBTN3,
format: boolFormat,
onchange: () => {
settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3;
updateOptions();
}
},
'Wake on FaceUp': {
value: settings.options.wakeOnFaceUp,
format: boolFormat,
onchange: () => {
settings.options.wakeOnFaceUp = !settings.options.wakeOnFaceUp;
updateOptions();
}
},
'Wake on Touch': {
value: settings.options.wakeOnTouch,
format: boolFormat,
onchange: () => {
settings.options.wakeOnTouch = !settings.options.wakeOnTouch;
updateOptions();
}
},
'Wake On Twist': {
value: settings.options.wakeOnTwist,
format: boolFormat,
onchange: () => {
settings.options.wakeOnTwist = !settings.options.wakeOnTwist;
updateOptions();
}
},
'Twist Threshold': {
value: internalToG(settings.options.twistThreshold),
min: -0.5,
max: 0.5,
step: 0.01,
onchange: v => {
settings.options.twistThreshold = gToInternal(v || 0.1);
updateOptions();
}
},
'Twist Max Y': {
value: settings.options.twistMaxY,
min: -1500,
max: 1500,
step: 100,
onchange: v => {
settings.options.twistMaxY = v || -800;
updateOptions();
}
},
'Twist Timeout': {
value: settings.options.twistTimeout,
min: 0,
max: 2000,
step: 100,
onchange: v => {
settings.options.twistTimeout = v || 1000;
updateOptions();
}
}
}
return E.showMenu(wakeUpMenu)
}
function showLocaleMenu() {
const localemenu = {
'': { 'title': 'Locale' },
@ -138,7 +255,7 @@ function showLocaleMenu() {
},
'Clock Style': {
value: !!settings["12hour"],
format : v => v?"12hr":"24hr",
format: v => v ? "12hr" : "24hr",
onchange: v => {
settings["12hour"] = v;
updateSettings();
@ -166,33 +283,33 @@ function showResetMenu() {
}
function makeConnectable() {
try { NRF.wake(); } catch(e) {}
try { NRF.wake(); } catch (e) { }
Bluetooth.setConsole(1);
var name="Bangle.js "+NRF.getAddress().substr(-5).replace(":","");
E.showPrompt(name+"\nStay Connectable?",{title:"Connectable"}).then(r=>{
if (settings.ble!=r) {
var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", "");
E.showPrompt(name + "\nStay Connectable?", { title: "Connectable" }).then(r => {
if (settings.ble != r) {
settings.ble = r;
updateSettings();
}
if (!r) try { NRF.sleep(); } catch(e) {}
if (!r) try { NRF.sleep(); } catch (e) { }
showMainMenu();
});
}
function showClockMenu() {
var clockApps = require("Storage").list(/\.info$/).map(app=>{
var clockApps = require("Storage").list(/\.info$/).map(app => {
try { return require("Storage").readJSON(app); }
catch (e) {}
}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
catch (e) { }
}).filter(app => app.type == "clock").sort((a, b) => a.sortorder - b.sortorder);
const clockMenu = {
'': {
'title': 'Select Clock',
},
'< Back': showMainMenu,
};
clockApps.forEach((app,index) => {
clockApps.forEach((app, index) => {
var label = app.name;
if ((!settings.clock && index === 0) || (settings.clock === app.src)) {
label = "* "+label;
label = "* " + label;
}
clockMenu[label] = () => {
if (settings.clock !== app.src) {
@ -203,7 +320,7 @@ function showClockMenu() {
};
});
if (clockApps.length === 0) {
clockMenu["No Clocks Found"] = () => {};
clockMenu["No Clocks Found"] = () => { };
}
return E.showMenu(clockMenu);
}
@ -215,7 +332,7 @@ function showSetTimeMenu() {
const timemenu = {
'': {
'title': 'Set Time',
'predraw': function() {
'predraw': function () {
d = new Date();
timemenu.Hour.value = d.getHours();
timemenu.Minute.value = d.getMinutes();
@ -234,7 +351,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setHours(v);
setTime(d.getTime()/1000);
setTime(d.getTime() / 1000);
}
},
'Minute': {
@ -245,7 +362,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setMinutes(v);
setTime(d.getTime()/1000);
setTime(d.getTime() / 1000);
}
},
'Second': {
@ -256,7 +373,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setSeconds(v);
setTime(d.getTime()/1000);
setTime(d.getTime() / 1000);
}
},
'Date': {
@ -267,7 +384,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setDate(v);
setTime(d.getTime()/1000);
setTime(d.getTime() / 1000);
}
},
'Month': {
@ -278,7 +395,7 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setMonth(v - 1);
setTime(d.getTime()/1000);
setTime(d.getTime() / 1000);
}
},
'Year': {
@ -289,16 +406,16 @@ function showSetTimeMenu() {
onchange: v => {
d = new Date();
d.setFullYear(v);
setTime(d.getTime()/1000);
setTime(d.getTime() / 1000);
}
}
};
return E.showMenu(timemenu);
}
function showAppSettingsMenu(){
function showAppSettingsMenu() {
let appmenu = {
'': {'title': 'App Settings'},
'': { 'title': 'App Settings' },
'< Back': showMainMenu,
}
const apps = storage.list(/\.info$/)
@ -306,10 +423,10 @@ function showAppSettingsMenu(){
.filter(app => app && app.settings)
.sort((a, b) => a.sortorder - b.sortorder)
if (apps.length === 0) {
appmenu['No app has settings'] = () => {};
appmenu['No app has settings'] = () => { };
}
apps.forEach(function (app) {
appmenu[app.name] = () => {showAppSettings(app)};
appmenu[app.name] = () => { showAppSettings(app) };
})
E.showMenu(appmenu)
}

View File

@ -1,2 +1,4 @@
0.01: New App!
0.02: Add swipe support and doucle tap to run application
0.02: Add swipe support and doucle tap to run application
0.03: Close launcher when lcd turn off
0.04: Complete rewrite to add animation and loop ( issue #210 )

View File

@ -1,4 +1,6 @@
Bangle.setLCDMode("120x120");
g.clear();
g.flip();
const Storage = require("Storage");
@ -14,99 +16,144 @@ function getApps(){
});
}
const selected = 0;
const apps = getApps();
const HEIGHT = g.getHeight();
const WIDTH = g.getWidth();
const HALF = WIDTH/2;
const ANIMATION_FRAME = 3;
const ANIMATION_STEP = HALF / ANIMATION_FRAME;
function prev(){
if (selected>=0) {
selected--;
}
drawMenu();
function getPosition(index){
return (index*HALF);
}
function next() {
if (selected+1<apps.length) {
selected++;
}
drawMenu();
let current_app = 0;
let target = 0;
let slideOffset = 0;
const back = {
name: 'BACK',
back: true
};
const apps = [back].concat(getApps());
apps.push(back);
function noIcon(x, y, size){
const half = size/2;
g.setColor(1,1,1);
g.setFontAlign(-0,0);
const fontSize = Math.floor(size / 30 * 2);
g.setFont('6x8', fontSize);
if(fontSize) g.drawString('-?-', x+1.5, y);
g.drawRect(x-half, y-half, x+half, y+half);
}
function drawIcons(offset){
apps.forEach((app, i) => {
const x = getPosition(i) + HALF - offset;
const y = HALF - (HALF*0.3);//-(HALF*0.7);
let diff = (x - HALF);
if(diff < 0) diff *=-1;
let size = 30;
if((diff*0.5) < size) size -= (diff*0.5);
else size = 0;
const scale = size / 30;
if(size){
let c = size / 30 * 2;
c = c -1;
if(c < 0) c = 0;
if(app.back){
g.setFont('6x8', 1);
g.setFontAlign(0, -1);
g.setColor(c,c,c);
g.drawString('Back', HALF, HALF);
return;
}
// icon
const icon = app.icon ? Storage.read(app.icon) : null;
if(icon){
try {
g.drawImage(icon, x-(scale*24), y-(scale*24), { scale: scale });
} catch(e){
noIcon(x, y, size);
}
}else{
noIcon(x, y, size);
}
//text
g.setFont('6x8', 1);
g.setFontAlign(0, -1);
g.setColor(c,c,c);
g.drawString(app.name, HALF, HEIGHT - (HALF*0.7));
const type = app.type ? app.type : 'App';
const version = app.version ? app.version : '0.00';
const info = type+' v'+version;
g.setFontAlign(0,1);
g.setFont('4x6', 0.25);
g.setColor(c,c,c);
g.drawString(info, HALF, 110, { scale: scale });
}
});
}
function draw(ignoreLoop){
g.clear();
drawIcons(slideOffset);
g.flip();
if(slideOffset == target) return;
if(slideOffset < target) slideOffset+= ANIMATION_STEP;
else if(slideOffset > target) slideOffset -= ANIMATION_STEP;
if(!ignoreLoop) draw();
}
function animateTo(index){
target = getPosition(index);
draw();
}
function goTo(index){
current_app = index;
target = getPosition(index);
slideOffset = target;
draw(true);
}
goTo(1);
function prev(){
if(current_app == 0) goTo(apps.length-1);
current_app -= 1;
if(current_app < 0) current_app = 0;
animateTo(current_app);
}
function next(){
if(current_app == apps.length-1) goTo(0);
current_app += 1;
if(current_app > apps.length-1) current_app = apps.length-1;
animateTo(current_app);
}
function run() {
if(selected < 0) return load();
if (!apps[selected].src) return;
if (Storage.read(apps[selected].src)===undefined) {
const app = apps[current_app];
if(app.back) return load();
if (Storage.read(app.src)===undefined) {
E.showMessage("App Source\nNot found");
setTimeout(drawMenu, 2000);
setTimeout(draw, 2000);
} else {
E.showMessage("Loading...");
load(apps[selected].src);
}
}
function getCurrentApp(){
return apps[selected];
}
function getNextApp(){
return apps[selected+1];
}
function drawFallbackIcon(){
g.setColor(1,1,1);
g.fillRect(72, 40, 168, 136);
g.setColor(0,0,0);
g.setFont('6x8', 8);
g.drawString('?', 124, 88);
}
function drawArrow(x, y, size, dir){
size = size || 10;
dir = dir || 1;
g.moveTo(x, y).lineTo(x+(size*dir), y-size).lineTo(x+(size*dir),y+size).lineTo(x, y);
}
function drawMenu(){
if(selected < 0){
Bangle.setLCDMode();
g.clear();
g.setFontAlign(0,0);
g.setFont('6x8', 2);
g.drawString('Back', 120, 120);
drawArrow(220, 120, 10, -1);
return;
g.flip();
E.showMessage("Loading...");
load(app.src);
}
const app = getCurrentApp();
g.clear();
g.setFontAlign(0,0);
g.setFont('6x8', 2);
if(!app) return g.drawString('???', 120, 120);
g.drawString(app.name, 120, 160);
if (app.icon) icon = Storage.read(app.icon);
if (icon) try {g.drawImage(icon, 120-48, 40, { scale: 2 });} catch(e){ drawFallbackIcon(); }
else drawFallbackIcon();
g.setFont('6x8', 1);
const type = app.type ? app.type : 'App';
const version = app.version ? app.version : '0.00';
const info = type+' v'+version;
g.setFontAlign(-1,1);
g.drawString(info, 20, 220);
const count = (selected+1)+'/'+apps.length;
g.setFontAlign(1,1);
g.drawString(count, 220, 220);
drawArrow(20, 120, 10, 1);
if(getNextApp()) drawArrow(220, 120, 10, -1);
}
drawMenu();
// Physical buttons
setWatch(prev, BTN1, {repeat:true});
setWatch(next, BTN3, {repeat:true});
setWatch(prev, BTN1, { repeat: true });
setWatch(next, BTN3, { repeat: true });
setWatch(run, BTN2, {repeat:true,edge:"falling"});
// Screen event
@ -127,4 +174,9 @@ Bangle.on('touch', function(button){
Bangle.on('swipe', dir => {
if(dir == 1) prev();
else next();
});
// close launcher when lcd is off
Bangle.on('lcdPower', on => {
if(!on) return load();
});

View File

@ -2,3 +2,4 @@
0.02: Animate balloon intro
0.03: BTN3 now won't restart when at the end
0.04: Fix regression after tweaks to Storage.readJSON
0.05: Move configuration into App/widget settings

16
apps/welcome/settings.js Normal file
View File

@ -0,0 +1,16 @@
// The welcome app is special, and gets to use global settings
(function(back) {
let settings = require('Storage').readJSON('setting.json', 1) || {}
E.showMenu({
'': { 'title': 'Welcome App' },
'Run again': {
value: !settings.welcomed,
format: v => v ? 'Yes' : 'No',
onchange: v => {
settings.welcomed = v ? undefined : true
require('Storage').write('setting.json', settings)
},
},
'< Back': back,
})
})

View File

@ -3,3 +3,5 @@
0.04: Ensure redrawing works with variable size widget system
0.05: Change color depending on battery level, cloned from widbat
0.06: Show battery percentage as text
0.07: Add settings: percentage/color/charger icon
0.08: Draw percentage as inverted on monochrome battery

58
apps/widbatpc/settings.js Normal file
View File

@ -0,0 +1,58 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
(function(back) {
const SETTINGS_FILE = 'widbatpc.settings.json'
const COLORS = ['By Level', 'Green', 'Monochrome']
// initialize with default settings...
let s = {
'color': COLORS[0],
'percentage': true,
'charger': true,
}
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage')
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
for (const key in saved) {
s[key] = saved[key]
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function (value) {
s[key] = value
storage.write(SETTINGS_FILE, s)
WIDGETS["batpc"].reload()
}
}
const onOffFormat = b => (b ? 'on' : 'off')
const menu = {
'': { 'title': 'Battery Widget' },
'< Back': back,
'Percentage': {
value: s.percentage,
format: onOffFormat,
onchange: save('percentage'),
},
'Charging Icon': {
value: s.charger,
format: onOffFormat,
onchange: save('charger'),
},
'Color': {
format: () => s.color,
onchange: function () {
// cycles through options
const oldIndex = COLORS.indexOf(s.color)
const newIndex = (oldIndex + 1) % COLORS.length
s.color = COLORS[newIndex]
save('color')(s.color)
},
},
}
E.showMenu(menu)
})

View File

@ -1,20 +1,62 @@
(function(){
const DEFAULTS = {
'color': 'By Level',
'percentage': true,
'charger': true,
}
const COLORS = {
'white': -1,
'charging': 0x07E0, // "Green"
'high': 0x05E0, // slightly darker green
'ok': 0xFD20, // "Orange"
'low':0xF800, // "Red"
}
const SETTINGS_FILE = 'widbatpc.settings.json'
let settings
function loadSettings() {
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}
}
function setting(key) {
if (!settings) { loadSettings() }
return (key in settings) ? settings[key] : DEFAULTS[key]
}
const levelColor = (l) => {
if (Bangle.isCharging()) return 0x07E0; // "Green"
if (l >= 50) return 0x05E0; // slightly darker green
if (l >= 15) return 0xFD20; // "Orange"
return 0xF800; // "Red"
// "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
const green = setting('percentage') ? COLORS.high : COLORS.charging
switch (setting('color')) {
case 'Monochrome': return COLORS.white; // no chance of reading the percentage here :-(
case 'Green': return green;
case 'By Level': // fall through
default:
if (setting('charger')) {
// charger icon -> always make percentage readable
if (Bangle.isCharging() || l >= 50) return green;
} else {
// no icon -> brightest green to indicate charging, even when showing percentage
if (Bangle.isCharging()) return COLORS.charging;
if (l >= 50) return COLORS.high;
}
if (l >= 15) return COLORS.ok;
return COLORS.low;
}
}
const chargerColor = () => {
return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging
}
function setWidth() {
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
WIDGETS["batpc"].width = 40;
if (Bangle.isCharging() && setting('charger')) {
WIDGETS["batpc"].width += 16;
}
}
function draw() {
var s = 39;
var x = this.x, y = this.y;
const l = E.getBattery(), c = levelColor(l);
if (Bangle.isCharging()) {
g.setColor(c).drawImage(atob(
if (Bangle.isCharging() && setting('charger')) {
g.setColor(chargerColor()).drawImage(atob(
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
x+=16;
}
@ -22,18 +64,40 @@ function draw() {
g.fillRect(x,y+2,x+s-4,y+21);
g.clearRect(x+2,y+4,x+s-6,y+19);
g.fillRect(x+s-3,y+10,x+s,y+14);
g.setColor(c).fillRect(x+4,y+6,x+4+l*(s-12)/100,y+17);
const l = E.getBattery(),
c = levelColor(l);
const xl = x+4+l*(s-12)/100
g.setColor(c).fillRect(x+4,y+6,xl,y+17);
g.setColor(-1);
g.setFontAlign(-1,-1);
if (!setting('percentage')) {
return;
}
let gfx = g
if (setting('color') === 'Monochrome') {
// draw text inverted on battery level
gfx = Graphics.createCallback(240, 240, 1,
(x,y) => {g.setPixel(x,y,x<=xl?0:-1)})
}
gfx.setFontAlign(-1,-1);
if (l >= 100) {
g.setFont('4x6', 2);
g.drawString(l, x + 6, y + 7);
gfx.setFont('4x6', 2);
gfx.drawString(l, x + 6, y + 7);
} else {
if (l < 10) x+=6;
g.setFont('6x8', 2);
g.drawString(l, x + 6, y + 4);
gfx.setFont('6x8', 2);
gfx.drawString(l, x + 6, y + 4);
}
}
// reload widget, e.g. when settings have changed
function reload() {
loadSettings()
// need to redraw all widgets, because changing the "charger" setting
// can affect the width and mess with the whole widget layout
setWidth()
g.clear();
Bangle.drawWidgets();
}
Bangle.on('charging',function(charging) {
if(charging) Bangle.buzz();
setWidth();
@ -43,7 +107,7 @@ Bangle.on('charging',function(charging) {
var batteryInterval;
Bangle.on('lcdPower', function(on) {
if (on) {
WIDGETS["bat"].draw();
WIDGETS["batpc"].draw();
// refresh once a minute if LCD on
if (!batteryInterval)
batteryInterval = setInterval(draw, 60000);
@ -54,6 +118,6 @@ Bangle.on('lcdPower', function(on) {
}
}
});
WIDGETS["bat"]={area:"tr",width:40,draw:draw};
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
setWidth();
})()

6
apps/wohrm/ChangeLog Normal file
View File

@ -0,0 +1,6 @@
0.01: Only tested on the emulator.
0.02: Adapted to new App code layout
0.03: Optimized rendering for the background
0.04: Only buzz on high confidence (>85%)
0.05: Improved buzz timing and rendering
0.06: Removed debug outputs, fixed rendering for upper limit, improved rendering for +/- icons, changelog version order fixed

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4AVgnd5tABI3c7oJGAAUs5gAC4gJDpgJD4QWGhoMDAAPQBJYADBgoABBJYAChgJD5oDC4AJEAAfAC4fcBIfUDYYJEEogWCgQJEoYSHAAsgIw3MmYqIn89JAoXFn5DH4f/+YXFWQnE/4GEAAXP///ZgooE4X/ngvMPAQXEBoIXHHIJfDC4ss5nf+f9OosjFwgXF5oTBp8z+gMBMQPTn5dBNIgXCAwPDEQM/mQmCJQNP/8zDIJRDO4SnB6fz7k/poXEJwIJBmanGhvMl//loxC7nE/jUCon/6gzBC4PQC4MDKIJFDn9M4YXB5nUKYbACmAXBgE/+YMBOoMvngXDJIKDB6YvBOwRgDaoINB788p5wDn7HELwQABghWCBoPD/s/YwNN5i+Bc4dAC4bBCC4fyPIPU+Z0BDAZGEJAffYgPC+ZxBG4KkB6f/C4JGEAAQsBcIX/+QEBCgP9A4IXBCwwwB5pxDPYJoDcgIuIGASJH5rvBAwIWIeYQABl5jBAAXDIwLrCABCcC76gDAoP0RgwAFYYJ7DJAcsFxYABaYJ7DAAXECxhJEAAgWOPQgACIpoADUwb1BCyBJERZgYKkAXUglACygA/AH4AFA=="))

330
apps/wohrm/app.js Normal file
View File

@ -0,0 +1,330 @@
/* eslint-disable no-undef */
const Setter = {
NONE: "none",
UPPER: 'upper',
LOWER: 'lower'
};
const shortBuzzTimeInMs = 80;
const longBuzzTimeInMs = 400;
let upperLimit = 130;
let upperLimitChanged = true;
let lowerLimit = 100;
let lowerLimitChanged = true;
let limitSetter = Setter.NONE;
let currentHeartRate = 0;
let hrConfidence = -1;
let hrChanged = true;
let confidenceChanged = true;
let setterHighlightTimeout;
function renderUpperLimitBackground() {
g.setColor(1,0,0);
g.fillRect(125,40, 210, 70);
g.fillRect(180,70, 210, 200);
//Round top left corner
g.fillEllipse(115,40,135,70);
//Round top right corner
g.setColor(0,0,0);
g.fillRect(205,40, 210, 45);
g.setColor(1,0,0);
g.fillEllipse(190,40,210,50);
//Round inner corner
g.fillRect(174,71, 179, 76);
g.setColor(0,0,0);
g.fillEllipse(160,71,179,82);
//Round bottom
g.setColor(1,0,0);
g.fillEllipse(180,190, 210, 210);
}
function renderLowerLimitBackground() {
g.setColor(0,0,1);
g.fillRect(10, 180, 100, 210);
g.fillRect(10, 50, 40, 180);
//Rounded top
g.setColor(0,0,1);
g.fillEllipse(10,40, 40, 60);
//Round bottom right corner
g.setColor(0,0,1);
g.fillEllipse(90,180,110,210);
//Round inner corner
g.setColor(0,0,1);
g.fillRect(40,175,45,180);
g.setColor(0,0,0);
g.fillEllipse(41,170,60,179);
//Round bottom left corner
g.setColor(0,0,0);
g.fillRect(10,205, 15, 210);
g.setColor(0,0,1);
g.fillEllipse(10,200,30,210);
}
function drawTrainingHeartRate() {
//Only redraw if the display is on
if (Bangle.isLCDOn()) {
renderUpperLimit();
renderCurrentHeartRate();
renderLowerLimit();
renderConfidenceBars();
}
buzz();
}
function renderUpperLimit() {
if(!upperLimitChanged) { return; }
g.setColor(1,0,0);
g.fillRect(125,40, 210, 70);
if(limitSetter === Setter.UPPER){
g.setColor(255,255, 0);
} else {
g.setColor(255,255,255);
}
g.setFontVector(13);
g.drawString("Upper: " + upperLimit, 125, 50);
upperLimitChanged = false;
}
function renderCurrentHeartRate() {
if(!hrChanged) { return; }
g.setColor(255,255,255);
g.fillRect(55, 110, 165, 150);
g.setColor(0,0,0);
g.setFontVector(24);
g.setFontAlign(1, -1, 0);
g.drawString(currentHeartRate, 130, 117);
//Reset alignment to defaults
g.setFontAlign(-1, -1, 0);
hrChanged = false;
}
function renderLowerLimit() {
if(!lowerLimitChanged) { return; }
g.setColor(0,0,1);
g.fillRect(10, 180, 100, 210);
if(limitSetter === Setter.LOWER){
g.setColor(255,255, 0);
} else {
g.setColor(255,255,255);
}
g.setFontVector(13);
g.drawString("Lower: " + lowerLimit, 20,190);
lowerLimitChanged = false;
}
function renderConfidenceBars(){
if(!confidenceChanged) { return; }
if(hrConfidence >= 85){
g.setColor(0, 255, 0);
} else if (hrConfidence >= 50) {
g.setColor(255, 255, 0);
} else if(hrConfidence >= 0){
g.setColor(255, 0, 0);
} else {
g.setColor(255, 255, 255);
}
g.fillRect(45, 110, 55, 150);
g.fillRect(165, 110, 175, 150);
confidenceChanged = false;
}
function renderPlusMinusIcons() {
if (limitSetter === Setter.NONE) {
g.setColor(0, 0, 0);
} else {
g.setColor(1, 1, 1);
}
g.setFontVector(14);
//+ for Btn1
g.drawString("+", 222, 50);
//- for Btn3
g.drawString("-", 222,165);
return;
}
function renderHomeIcon() {
//Home for Btn2
g.setColor(1, 1, 1);
g.drawLine(220, 118, 227, 110);
g.drawLine(227, 110, 234, 118);
g.drawPoly([222,117,222,125,232,125,232,117], false);
g.drawRect(226,120,229,125);
}
function buzz() {
// Do not buzz if not confident
if(hrConfidence < 85) { return; }
if(currentHeartRate > upperLimit)
{
Bangle.buzz(shortBuzzTimeInMs);
setTimeout(() => { Bangle.buzz(shortBuzzTimeInMs); }, shortBuzzTimeInMs * 2);
}
if(currentHeartRate < lowerLimit)
{
Bangle.buzz(longBuzzTimeInMs);
}
}
function onHrm(hrm){
if(currentHeartRate !== hrm.bpm){
currentHeartRate = hrm.bpm;
hrChanged = true;
}
if(hrConfidence !== hrm.confidence) {
hrConfidence = hrm.confidence;
confidenceChanged = true;
}
}
function setLimitSetterToLower() {
resetHighlightTimeout();
limitSetter = Setter.LOWER;
upperLimitChanged = true;
lowerLimitChanged = true;
renderUpperLimit();
renderLowerLimit();
renderPlusMinusIcons();
}
function setLimitSetterToUpper() {
resetHighlightTimeout();
limitSetter = Setter.UPPER;
upperLimitChanged = true;
lowerLimitChanged = true;
renderLowerLimit();
renderUpperLimit();
renderPlusMinusIcons();
}
function setLimitSetterToNone() {
limitSetter = Setter.NONE;
upperLimitChanged = true;
lowerLimitChanged = true;
renderLowerLimit();
renderUpperLimit();
renderPlusMinusIcons();
}
function incrementLimit() {
resetHighlightTimeout();
if (limitSetter === Setter.UPPER) {
upperLimit++;
renderUpperLimit();
upperLimitChanged = true;
} else if(limitSetter === Setter.LOWER) {
lowerLimit++;
renderLowerLimit();
lowerLimitChanged = true;
}
}
function decrementLimit(){
resetHighlightTimeout();
if (limitSetter === Setter.UPPER) {
upperLimit--;
renderUpperLimit();
upperLimitChanged = true;
} else if(limitSetter === Setter.LOWER) {
lowerLimit--;
renderLowerLimit();
lowerLimitChanged = true;
}
}
function resetHighlightTimeout() {
if (setterHighlightTimeout) {
clearTimeout(setterHighlightTimeout);
}
setterHighlightTimeout = setTimeout(setLimitSetterToNone, 2000);
}
// Show launcher when middle button pressed
function switchOffApp(){
Bangle.setHRMPower(0);
Bangle.showLauncher();
}
// special function to handle display switch on
Bangle.on('lcdPower', (on) => {
g.clear();
if (on) {
Bangle.drawWidgets();
renderHomeIcon();
renderLowerLimitBackground();
renderUpperLimitBackground();
lowerLimitChanged = true;
upperLimitChanged = true;
drawTrainingHeartRate();
}
});
Bangle.setHRMPower(1);
Bangle.on('HRM', onHrm);
setWatch(incrementLimit, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(switchOffApp, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(decrementLimit, BTN3, {edge:"rising", debounce:50, repeat:true});
setWatch(setLimitSetterToLower, BTN4, {edge:"rising", debounce:50, repeat:true});
setWatch(setLimitSetterToUpper, BTN5, { edge: "rising", debounce: 50, repeat: true });
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
//drawTrainingHeartRate();
renderHomeIcon();
renderLowerLimitBackground();
renderUpperLimitBackground();
// refesh every sec
setInterval(drawTrainingHeartRate, 1000);

BIN
apps/wohrm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

9
browserconfig.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="img/mstile-150x150.png"/>
<TileColor>#5755d9</TileColor>
</tile>
</msapplication>
</browserconfig>

24
css/pwa.css Normal file
View File

@ -0,0 +1,24 @@
.hidden {
display: none !important;
}
#installContainer {
position: absolute;
bottom: 1em;
display: flex;
justify-content: center;
width: 100%;
}
#installContainer button {
background-color: inherit;
border: 1px solid white;
color: white;
font-size: 1em;
padding: 0.75em;
}
.floating {
position: fixed;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
img/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

BIN
img/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

BIN
img/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

100
img/safari-pinned-tab.svg Normal file
View File

@ -0,0 +1,100 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="960.000000pt" height="960.000000pt" viewBox="0 0 960.000000 960.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,960.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M3632 9573 c-94 -64 -135 -159 -146 -338 -3 -38 -7 -90 -10 -115 -3
-25 -8 -70 -11 -100 -2 -30 -7 -77 -9 -105 -3 -27 -8 -79 -11 -115 -3 -36 -8
-87 -11 -115 -8 -77 -14 -136 -18 -180 -4 -44 -14 -130 -20 -185 -3 -19 -10
-80 -16 -135 -6 -55 -13 -111 -16 -125 -4 -25 -13 -91 -19 -140 -15 -123 -21
-146 -47 -183 -31 -43 -47 -75 -52 -103 -3 -13 -7 -27 -9 -32 -10 -15 -19 -99
-19 -174 l0 -77 -36 -23 c-96 -58 -185 -119 -276 -188 -54 -41 -108 -82 -120
-90 -21 -15 -332 -326 -351 -351 -10 -13 -91 -116 -120 -151 -22 -27 -180
-271 -193 -298 -7 -14 -29 -57 -50 -96 -42 -77 -134 -283 -148 -329 -2 -5 -10
-30 -19 -55 -19 -52 -71 -223 -79 -260 -3 -14 -10 -43 -15 -65 -6 -22 -12 -53
-15 -70 -3 -16 -8 -41 -11 -55 -9 -43 -17 -104 -32 -240 -16 -149 -9 -530 13
-670 16 -101 34 -203 49 -270 21 -94 21 -92 27 -110 14 -42 39 -127 43 -145 2
-11 12 -41 24 -68 11 -26 21 -51 22 -55 1 -4 5 -17 10 -30 5 -13 9 -25 9 -27
0 -13 117 -257 164 -340 38 -68 98 -168 106 -175 3 -3 25 -34 50 -70 44 -66
130 -177 185 -239 58 -67 170 -185 194 -205 14 -12 34 -30 45 -41 12 -12 50
-45 86 -75 36 -30 67 -57 70 -60 3 -3 21 -17 40 -30 19 -13 37 -27 40 -30 10
-11 136 -97 212 -144 l76 -47 0 -77 c-1 -131 22 -224 68 -287 30 -39 40 -68
49 -135 4 -30 8 -64 10 -75 2 -11 6 -40 9 -65 3 -25 8 -55 10 -67 3 -12 7 -48
11 -80 3 -32 8 -76 11 -98 3 -22 7 -56 9 -75 2 -19 6 -60 9 -90 8 -70 13 -119
21 -200 4 -36 8 -81 11 -100 3 -19 7 -60 9 -90 2 -30 6 -75 9 -100 3 -25 8
-76 11 -115 3 -38 8 -88 10 -110 2 -22 7 -78 11 -125 11 -132 15 -152 51 -223
31 -62 84 -114 131 -127 24 -6 378 -10 1019 -10 976 0 983 0 1019 21 21 11 46
31 56 45 11 13 26 31 34 39 9 8 12 15 8 15 -4 0 0 8 8 18 13 14 19 30 33 82 5
22 16 109 20 175 3 44 8 94 10 110 2 17 7 60 10 95 11 137 16 186 20 225 3 22
7 65 10 95 2 30 7 75 10 100 8 70 14 130 20 190 8 74 14 128 20 170 10 71 16
118 20 160 2 24 7 61 10 84 24 164 35 209 60 246 52 76 66 128 72 270 3 84 7
108 21 118 9 7 73 49 142 94 68 44 142 95 164 114 46 40 50 40 82 15 22 -18
50 -20 142 -12 23 2 303 284 319 321 20 46 17 112 -6 151 l-20 32 35 46 c18
25 37 51 42 56 52 68 192 286 192 301 0 2 11 22 25 44 14 22 23 40 19 40 -3 0
4 13 15 29 12 16 21 32 21 35 0 5 17 45 70 161 21 48 108 303 114 336 3 13 5
24 6 24 1 0 3 7 5 15 2 8 12 55 23 104 12 50 19 93 16 97 -2 4 0 10 6 14 6 4
8 10 5 15 -11 17 20 23 119 23 55 0 113 4 128 9 57 20 58 28 58 485 -1 313 -4
422 -13 434 -23 29 -66 51 -99 49 -98 -6 -195 1 -195 13 -2 85 -133 550 -164
582 -5 5 -9 19 -9 32 0 13 -4 23 -10 23 -5 0 -10 6 -10 14 0 8 -6 27 -14 43
-8 15 -20 42 -27 58 -8 17 -26 51 -41 78 -15 26 -26 47 -24 47 4 0 -78 142
-104 180 -49 75 -60 92 -60 100 0 4 -3 10 -7 12 -5 2 -34 38 -66 81 -53 71
-57 80 -44 95 14 16 34 76 32 97 -7 62 -24 86 -154 218 -138 141 -187 177
-239 177 -35 0 -94 -19 -104 -34 -4 -6 -27 5 -60 27 -29 20 -87 60 -128 88
-141 98 -158 109 -168 109 -23 0 -31 31 -33 124 -2 112 -22 191 -67 264 -38
62 -46 89 -56 194 -2 19 -6 43 -9 54 -3 10 -9 50 -12 89 -4 38 -8 72 -10 75
-1 3 -5 37 -9 75 -4 39 -8 81 -11 95 -2 14 -6 54 -9 90 -4 36 -8 76 -10 90 -2
14 -7 57 -10 95 -4 39 -9 85 -11 103 -2 18 -7 65 -10 105 -3 40 -7 83 -9 97
-3 14 -7 61 -10 105 -4 44 -8 91 -10 105 -3 14 -7 68 -11 120 -6 107 -13 156
-24 185 -21 55 -57 118 -83 146 -60 62 -4 59 -1091 59 l-994 0 -41 -27z m1338
-2207 c14 -4 48 -9 75 -12 28 -2 57 -7 65 -10 8 -3 35 -8 59 -10 23 -2 52 -9
62 -15 10 -5 19 -8 19 -5 0 3 15 0 33 -5 17 -6 43 -12 57 -15 14 -3 52 -14 85
-25 33 -11 67 -22 75 -24 53 -11 301 -119 390 -168 30 -17 69 -38 86 -48 46
-26 208 -132 214 -141 3 -4 23 -19 45 -34 90 -61 331 -289 410 -389 22 -27 43
-52 46 -55 26 -21 219 -310 219 -328 0 -6 4 -12 8 -14 25 -10 176 -355 217
-493 9 -33 20 -67 23 -75 7 -20 9 -29 17 -69 3 -19 8 -37 10 -41 4 -7 10 -31
30 -143 28 -152 36 -250 37 -442 0 -131 -2 -185 -17 -320 -4 -32 -13 -95 -21
-132 -2 -13 -6 -35 -9 -50 -13 -69 -31 -151 -35 -158 -1 -3 -3 -8 -4 -12 -11
-40 -30 -106 -43 -143 -8 -25 -16 -52 -18 -60 -13 -52 -144 -334 -198 -425
-55 -93 -189 -288 -220 -320 -7 -7 -28 -32 -47 -56 -39 -49 -224 -235 -285
-288 -169 -144 -528 -373 -567 -363 -5 1 -8 -3 -8 -8 0 -6 -4 -10 -10 -10 -5
0 -33 -11 -62 -24 -128 -58 -380 -142 -453 -152 -11 -1 -56 -9 -100 -18 -179
-35 -255 -41 -485 -40 -176 0 -289 6 -345 18 -11 2 -41 7 -67 10 -26 4 -69 11
-95 16 -26 6 -59 13 -73 15 -14 3 -32 8 -40 11 -8 3 -26 8 -40 11 -32 6 -109
29 -180 55 -30 11 -59 21 -65 22 -5 2 -21 8 -35 14 -14 6 -68 30 -120 52 -96
42 -191 92 -265 139 -22 14 -42 26 -45 26 -3 0 -16 8 -30 18 -183 131 -191
138 -269 202 -129 108 -337 337 -432 475 -24 35 -50 71 -57 78 -6 7 -12 17
-12 22 0 5 -6 15 -13 22 -25 28 -154 276 -191 368 -10 25 -23 56 -29 70 -6 14
-11 27 -12 30 -4 17 -48 148 -55 165 -19 46 -71 293 -85 410 -23 183 -24 487
-1 640 3 22 8 56 10 75 29 234 133 551 262 799 53 103 166 287 204 334 5 7 42
53 82 102 39 50 119 138 177 196 133 132 333 299 360 299 6 0 11 4 11 9 0 15
185 123 340 198 81 39 254 110 285 118 6 1 17 5 25 8 18 7 91 30 105 33 201
49 326 72 435 80 41 3 77 8 79 10 7 6 453 -3 481 -10z"/>
<path d="M5703 6467 c-137 -211 -282 -432 -292 -444 -6 -7 -16 -24 -23 -38 -6
-14 -14 -25 -18 -25 -4 0 -12 -11 -18 -25 -7 -14 -16 -31 -22 -38 -6 -7 -39
-57 -75 -112 -36 -55 -69 -107 -75 -115 -5 -8 -48 -73 -94 -145 -47 -71 -90
-135 -95 -142 -6 -7 -11 -17 -11 -23 0 -5 -4 -10 -10 -10 -5 0 -10 -4 -10 -9
0 -5 -37 -64 -81 -131 l-81 -123 23 -18 c41 -31 72 -45 76 -34 3 10 84 134
123 190 12 17 71 107 132 200 61 94 136 208 167 255 31 47 58 90 61 95 12 24
66 100 73 103 4 2 7 7 7 12 0 4 36 62 80 129 44 66 79 124 78 127 -2 3 5 10
15 15 9 6 17 15 17 20 0 6 20 39 45 75 25 35 45 68 45 73 0 5 3 11 8 13 4 2
33 43 65 92 l57 89 -42 29 c-24 15 -45 28 -48 28 -3 0 -38 -51 -77 -113z"/>
<path d="M4619 4871 c-67 -56 -33 -161 53 -161 56 0 98 42 93 94 -7 79 -88
115 -146 67z"/>
<path d="M5257 4523 c-73 -25 -152 -107 -173 -179 -26 -92 -10 -184 46 -250
21 -25 92 -76 110 -80 3 0 21 -8 40 -16 19 -8 78 -32 130 -52 120 -46 152 -66
188 -115 24 -33 29 -51 31 -105 5 -146 -85 -218 -267 -212 -104 4 -187 51
-251 144 l-23 32 -28 -20 c-15 -11 -33 -20 -38 -20 -24 0 -21 -22 7 -62 89
-129 205 -188 361 -182 92 3 110 7 168 36 100 48 161 126 178 228 12 75 4 133
-28 196 -41 83 -104 124 -321 208 -133 52 -161 69 -189 123 -25 45 -20 132 9
171 55 73 196 88 316 33 24 -11 50 -27 57 -36 18 -22 31 -19 68 16 l33 31 -23
25 c-12 13 -55 40 -95 60 -65 32 -83 36 -165 39 -68 2 -106 -1 -141 -13z"/>
<path d="M4626 4523 c-3 -4 -6 -357 -6 -785 l0 -779 38 2 c21 2 43 0 49 -4 6
-4 14 0 17 8 8 23 7 1555 -1 1556 -47 6 -92 7 -97 2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -6,6 +6,16 @@
<link rel="stylesheet" href="css/spectre.min.css">
<link rel="stylesheet" href="css/spectre-exp.min.css">
<link rel="stylesheet" href="css/spectre-icons.min.css">
<link rel="stylesheet" href="css/pwa.css">
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="site.webmanifest">
<link rel="mask-icon" href="img/safari-pinned-tab.svg" color="#5755d9">
<meta name="apple-mobile-web-app-title" content="BangleApps">
<meta name="application-name" content="BangleApps">
<meta name="msapplication-TileColor" content="#5755d9">
<meta name="theme-color" content="#5755d9">
<title>Bangle.js App Loader</title>
<style>
.navbar { background-color: #5755d9; padding: 0.5em 1em 0.5em 1em; }
@ -55,6 +65,17 @@
</section>-->
</header>
<div class="floating">
<p id="requireHTTPS" class="hidden">
<b>STOP!</b> This page <b>must</b> be served over HTTPS.
Please <a>reload this page via HTTPS</a>.
</p>
</div>
<div class="container" style="padding-top:4px">
<p><b>Note:</b> If you have a version of Bangle.js firmware before 2v04, please update to the <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">latest firmware</a> or
<a href="https://banglejs.com/oldapps/">use the legacy app loader</a>.
@ -117,7 +138,7 @@
</div>
<div class="container" style="padding-top: 8px;">
<p>Check out <a href="https://github.com/espruino/BangleApps" target="_blank">the Source on GitHub</a>, or
find out <a href="https://www.espruino.com/Bangle.js+App+Loader" target="_blank">how to add your own app</p>
find out <a href="https://www.espruino.com/Bangle.js+App+Loader" target="_blank">how to add your own app</a></p>
<p>Using <a href="https://espruino.com/" target="_blank">Espruino</a>, Icons from <a href="https://icons8.com/" target="_blank">icons8.com</a></p>
<h3>Utilities</h3>
@ -127,6 +148,15 @@
</div>
</div>
<footer class="floating hidden">
<!-- Install button, hidden by default -->
<div id="installContainer" class="hidden">
<button id="butInstall" type="button">
Install
</button>
</div>
</footer>
<script src="https://www.puck-js.com/puck.js"></script>
<script src="js/utils.js"></script>
<script src="js/ui.js"></script>
@ -134,5 +164,6 @@
<script src="js/appinfo.js"></script>
<script src="js/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="js/pwa.js" defer></script>
</body>
</html>

53
js/pwa.js Normal file
View File

@ -0,0 +1,53 @@
const divInstall = document.getElementById('installContainer');
const butInstall = document.getElementById('butInstall');
window.addEventListener('beforeinstallprompt', (event) => {
console.log('👍', 'beforeinstallprompt', event);
// Stash the event so it can be triggered later.
window.deferredPrompt = event;
// Remove the 'hidden' class from the install button container
divInstall.classList.toggle('hidden', false);
});
butInstall.addEventListener('click', () => {
console.log('👍', 'butInstall-clicked');
const promptEvent = window.deferredPrompt;
if (!promptEvent) {
// The deferred prompt isn't available.
return;
}
// Show the install prompt.
promptEvent.prompt();
// Log the result
promptEvent.userChoice.then((result) => {
console.log('👍', 'userChoice', result);
// Reset the deferred prompt variable, since
// prompt() can only be called once.
window.deferredPrompt = null;
// Hide the install button.
divInstall.classList.toggle('hidden', true);
});
});
window.addEventListener('appinstalled', (event) => {
console.log('👍', 'appinstalled', event);
});
/* Only register a service worker if it's supported */
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('js/service-worker.js');
}
/**
* Warn the page must be served over HTTPS
* The `beforeinstallprompt` event won't fire if the page is served over HTTP.
* Installability requires a service worker with a fetch event handler, and
* if the page isn't served over HTTPS, the service worker won't load.
*/
if (window.location.protocol === 'http:') {
const requireHTTPS = document.getElementById('requireHTTPS');
const link = requireHTTPS.querySelector('a');
link.href = window.location.href.replace('http://', 'https://');
requireHTTPS.classList.remove('hidden');
}

14
js/service-worker.js Normal file
View File

@ -0,0 +1,14 @@
self.addEventListener('install', (event) => {
console.log('👷', 'install', event);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.log('👷', 'activate', event);
return self.clients.claim();
});
self.addEventListener('fetch', function(event) {
// console.log('👷', 'fetch', event);
event.respondWith(fetch(event.request));
});

22
site.webmanifest Normal file
View File

@ -0,0 +1,22 @@
{
"name": "BangleApps",
"short_name": "BangleApps",
"description": "Banglejs App Store",
"icons": [
{
"src": "img/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#5755d9",
"background_color": "#5755d9",
"display": "standalone",
"start_url": "https://banglejs.com/apps/",
"scope": "https://banglejs.com/apps/"
}