1
0
Fork 0

Merge branch 'master' into new_alarm

master
Gordon Williams 2022-04-04 15:07:42 +01:00
commit 30e94e15ad
65 changed files with 75528 additions and 94 deletions

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"aclock.app.js","url":"clock-analog.js"},

View File

@ -8,4 +8,5 @@
0.06: fixes #1271 - wrong settings name
when weekday name and calendar weeknumber are on then display is <weekday short> #<calweek>
week is buffered until date or timezone changes
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
0.07: align default settings with app.js (otherwise the initial displayed settings will be confusing to users)
0.08: fixed calendar weeknumber not shortened to two digits

View File

@ -99,7 +99,7 @@ function updateState() {
}
function isoStr(date) {
return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).substr(-2) + "-" + ("0" + date.getDate()).substr(-2);
return date.getFullYear() + "-" + ("0" + (date.getMonth() + 1)).slice(-2) + "-" + ("0" + date.getDate()).slice(-2);
}
var calWeekBuffer = [false,false,false]; //buffer tz, date, week no (once calculated until other tz or date is requested)
@ -140,7 +140,7 @@ function draw() {
g.setFontAlign(0, 0).setFont("Anton").drawString(timeStr, x, y); // draw time
if (secondsScreen) {
y += 65;
var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).substr(-2);
var secStr = (secondsWithColon ? ":" : "") + ("0" + date.getSeconds()).slice(-2);
if (doColor())
g.setColor(0, 0, 1);
g.setFont("AntonSmall");
@ -193,7 +193,7 @@ function draw() {
if (calWeek || weekDay) {
var dowcwStr = "";
if (calWeek)
dowcwStr = " #" + ("0" + ISO8601calWeek(date)).substring(-2);
dowcwStr = " #" + ("0" + ISO8601calWeek(date)).slice(-2);
if (weekDay)
dowcwStr = require("locale").dow(date, calWeek ? 1 : 0) + dowcwStr; //weekDay e.g. Monday or weekDayShort #<calWeek> e.g. Mon #01
else //week #01

View File

@ -1,7 +1,7 @@
{
"id": "antonclk",
"name": "Anton Clock",
"version": "0.07",
"version": "0.08",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",

36
apps/bee/README.md Normal file
View File

@ -0,0 +1,36 @@
# Spelling bee game
Word finding game inspired by the NYT spelling bee. Find as many words with 4 or more letters (must include the
letter at the center of the 'hive') as you can.
## Usage
- tap on letters to type out word
- swipe left to delete last letter
- swipe right to enter; the word will turn blue while it is being checked against the internal dictionary; once
checked, it will turn red if the word is invalid, does not contain the central letter or has been guessed before or
will turn green if it is a valid word; in the latter case, points will be awarded
- swipe down to shuffle the 6 outer letters
- swipe up to view a list of already guessed words; tap on any of them to return to the regular game.
## Scoring
The number of correctly guessed words is displayed on the bottom left, the score on the bottom right. A single point
is awarded for a 4 letter word, or the number of letters if longer. A pangram is a word that contains all 7 letters at
least once and yields an additional 7 points. Each game contains at least one pangram.
## Technical remarks
The game uses an internal dictionary consisting of a newline separated list of English words ('bee.words', using the '2of12inf' word list).
The dictionary is fairly large (~700kB of flash space) and thus requires appropriate space on the watch and will make installing the app somewhat
slow. Because of its size it cannot be compressed (heatshrink needs to hold the compressed/uncompressed data in memory).
In order to make checking the validity of a guessed word faster an index file ('bee_lindex.json') is installed with
the app that facilitates faster word lookups. This index file is specific to the dictionary file used. If one were to
replace the dictionary file with a different version (e.g. a different language) the index file has to be regenerated. The easiest
way to do so is to delete (via the Web IDE or the fileman app on the watch) the file 'bee_lindex.json' - it will be regenerated (and saved,
i.e. it only happens once) on app startup automatically, a process that takes roughly 30 seconds.
![Screenshot](./bee_screenshot.png)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2JAAIKHnc7DyNPp4vRGAwuBGB4sBAAQvSGIovPFqYvHGAYvDGBYsGGhwvGGIQvEGBQnDMYhkNGBAvOvQABqyRTF5GJr4wLFwQACX6IwLsowJLYMrldVGAQvTsoADGBITD0YvDldPF6n+F4gyGGAdP5nMF4KKBGDJZDGI7EBcoOiGAK7DGAQvYRogxEr1Pp9VMAiSBBILBWeJIxCromBMAQwDAAZfTGBQyCxOCGAIvBGIV/F7AwMAAOIp95GAYACFqoyQMAIwGF7QADEQd5FgIADqvGF8DnEAAIvFGIWjF8CFE0QwHAAQudAAK0EGBQuecw3GqpemYIxiCGIa8cF4wwHdTwvJp9/F82jGA9VMQovf5jkHGIwvg4wvIAAgvg5miF9wwNF8QABF9QwF0YuoF4oxCqoulGBAAB42i0QvjGBPMF0gwIFswwHF1IA/AH4A/AH4AL"))

BIN
apps/bee/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

193
apps/bee/bee.app.js Normal file
View File

@ -0,0 +1,193 @@
const S = require("Storage");
var letters = [];
var letterIdx = [];
var centers = [];
var word = '';
var foundWords = [];
var score = 0;
var intervalID = -1;
function prepareLetterIdx () {
"compile"
var li = [0];
if (S.read("bee_lindex.json")!==undefined) li = S.readJSON("bee_lindex.json"); // check for cached index
else {
for (var i=1; i<26; ++i) {
var prefix = String.fromCharCode(97+i%26);
console.log(prefix);
li.push(S.read('bee.words').indexOf("\n"+prefix, li[i-1])+1);
}
li.push(S.read('bee.words').length);
S.writeJSON("bee_lindex.json", li);
}
for (var i=0; i<26; ++i) letterIdx[i] = S.read("bee.words", li[i], li[i+1]-li[i]);
}
function findWord (w) {
"compile"
var ci = w.charCodeAt(0)-97;
var f = letterIdx[ci].indexOf(w);
if (f>=0 && letterIdx[ci][f+w.length]=="\n") return true;
return false;
}
function isPangram(w) {
var ltrs = '';
for (var i=0; i<w.length; ++i) if (ltrs.indexOf(w[i])===-1) ltrs += w[i];
return ltrs.length==7;
}
function checkWord (w) {
if (w.indexOf(String.fromCharCode(97+letters[0]))==-1) return false; // does it contain central letter?
if (foundWords.indexOf(w)>=0) return false; // already found
if (findWord(w)) {
foundWords.push(w);
if (w.length==4) score++;
else score += w.length;
if (isPangram(w)) score += 7;
return true;
}
return false;
}
function getHexPoly(cx, cy, r, a) {
var p = [];
for (var i=0; i<6; ++i) p.push(cx+r*Math.sin((i+a)/3*Math.PI), cy+r*Math.cos((i+a)/3*Math.PI));
return p;
}
function drawHive() {
w = g.getWidth();
h = g.getHeight();
const R = w/3.3;
centers = getHexPoly(w/2, h/2+10, R, 0);
centers.push(w/2, h/2+10);
g.clear();
g.setFont("Vector", w/7).setFontAlign(0, 0, 0);
g.setColor(g.theme.fg);
for (var i=0; i<6; ++i) {
g.drawPoly(getHexPoly(centers[2*i], centers[2*i+1], 0.9*R/Math.sqrt(3), 0.5), {closed:true});
g.drawString(String.fromCharCode(65+letters[i+1]), centers[2*i]+2, centers[2*i+1]+2);
}
g.setColor(1, 1, 0).fillPoly(getHexPoly(w/2, h/2+10, 0.9*R/Math.sqrt(3), 0.5));
g.setColor(0).drawString(String.fromCharCode(65+letters[0]), w/2+2, h/2+10+2);
}
function shuffleLetters(qAll) {
for (var i=letters.length-1; i > 0; i--) {
var j = (1-qAll) + Math.floor(Math.random()*(i+qAll));
var temp = letters[i];
letters[i] = letters[j];
letters[j] = temp;
}
}
function pickLetters() {
var ltrs = "";
while (ltrs.length!==7) {
ltrs = [];
var j = Math.floor(26*Math.random());
var i = Math.floor((letterIdx[j].length-10)*Math.random());
while (letterIdx[j][i]!="\n" && i<letterIdx[j].length) ++i;
if (i<letterIdx[j].length-1) {
++i;
while (letterIdx[j][i]!=="\n") {
var c = letterIdx[j][i];
if (ltrs.indexOf(c)===-1) ltrs += c;
++i;
}
}
}
for (var i=0; i<7; ++i) letters.push(ltrs.charCodeAt(i)-97);
shuffleLetters(1);
}
function drawWord(c) {
g.clearRect(0, 0, g.getWidth()-1, 19);
g.setColor(c).setFont("Vector", 20).setFontAlign(0, 0, 0).drawString(word, g.getWidth()/2, 11).flip();
}
function touchHandler(e, x) {
var hex = 0;
var hex_d = 1e6;
for (var i=0; i<7; ++i) {
var d = (x.x-centers[2*i])*(x.x-centers[2*i]) + (x.y-centers[2*i+1])*(x.y-centers[2*i+1]);
if (d < hex_d) {
hex_d = d;
hex = i+1;
}
}
hex = hex%7;
if (word.length <= 15) word += String.fromCharCode(letters[hex]+65);
drawWord(g.theme.fg);
}
function drawScore() {
g.setColor(g.theme.fg).setFont("Vector", 20).setFontAlign(0, 0, 0);
g.clearRect(0, g.getHeight()-22, 60, g.getHeight()-1);
g.clearRect(g.getWidth()-60, g.getHeight()-22, g.getWidth(), g.getHeight()-1);
g.drawString(foundWords.length.toString(), 30, g.getHeight()-11);
g.drawString(score.toString(), g.getWidth()-30, g.getHeight()-11);
}
function wordFound (c) {
word = "";
drawWord(g.theme.fg);
drawScore();
clearInterval(intervalID);
intervalID = -1;
}
function swipeHandler(d, e) {
if (d==-1 && word.length>0) {
word = word.slice(0, -1);
drawWord(g.theme.fg);
}
if (d==1 && word.length>=4) {
drawWord("#00f");
drawWord((checkWord(word.toLowerCase()) ? "#0f0" : "#f00"));
if (intervalID===-1) intervalID = setInterval(wordFound, 800);
}
if (e===1) {
shuffleLetters(0);
drawHive();
drawScore();
drawWord(g.theme.fg);
}
if (e===-1 && foundWords.length>0) showWordList();
}
function showWordList() {
Bangle.removeListener("touch", touchHandler);
Bangle.removeListener("swipe", swipeHandler);
E.showScroller({
h : 20, c : foundWords.length,
draw : (idx, r) => {
g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setFont("6x8:2");
g.setColor(isPangram(foundWords[idx])?'#0f0':g.theme.fg).drawString(foundWords[idx].toUpperCase(),r.x+10,r.y+4);
},
select : (idx) => {
setInterval(()=> {
E.showScroller();
drawHive();
drawScore();
drawWord(g.theme.fg);
Bangle.on("touch", touchHandler);
Bangle.on("swipe", swipeHandler);
clearInterval();
}, 100);
}
});
}
prepareLetterIdx();
pickLetters();
drawHive();
drawScore();
Bangle.on("touch", touchHandler);
Bangle.on("swipe", swipeHandler);

1
apps/bee/bee_lindex.json Normal file
View File

@ -0,0 +1 @@
[0,41048,80445,152390,198606,228714,257919,279071,303726,337982,343582,348026,367246,404452,419780,438696,496250,499697,544600,624304,659085,680996,691270,708186,708341,709916,710883]

BIN
apps/bee/bee_screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

74578
apps/bee/bee_words_2of12 Normal file

File diff suppressed because it is too large Load Diff

16
apps/bee/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{ "id": "bee",
"name": "Bee",
"shortName":"Bee",
"icon": "app.png",
"version":"0.01",
"description": "Spelling bee",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"tags": "game,text",
"storage": [
{"name":"bee.app.js","url":"bee.app.js"},
{"name":"bee.words","url":"bee_words_2of12"},
{"name":"bee_lindex.json","url":"bee_lindex.json"},
{"name":"bee.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -7,6 +7,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"berlin-clock-screenshot.png"}],
"storage": [

2
apps/bordle/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App
0.02: app keeps track of statistics now

View File

@ -21,7 +21,8 @@ function buttonPushed(b) {
layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg;
layout.bt2.label = "<del>";
layout.bt4.label = "<ent>";
layout.bt3.label = layout.bt5.label = " ";
layout.bt3.label = " ";
layout.bt5.label = "<stat>";
layout.bt6.label = "<";
}
}
@ -30,6 +31,10 @@ function buttonPushed(b) {
if (b!=6) {
if ((keyStateIdx<=5 || b<=1) && inp.length<5) inp += String.fromCharCode(b+(keyStateIdx-1)*5+64);
else if (layout.input.label.length>0 && b==2) inp = inp.slice(0,-1);
if (keyStateIdx==6 && b==5) {
wordle.drawStats();
return;
}
layout.input.label = inp;
}
layout = getKeyLayout(inp);
@ -82,6 +87,7 @@ class Wordle {
this.word = this.words.slice(i, i+5).toUpperCase();
}
console.log(this.word);
this.stats = require("Storage").readJSON("bordlestats.json") || {'1':0, '2':0, '3':0, '4':0, '5':0, '6':0, 'p':0, 'w':0, 's':0, 'ms':0};
}
render(clear) {
h = g.getHeight();
@ -109,7 +115,7 @@ class Wordle {
layout = getKeyLayout("");
wordle.render(true);
});
return 3;
return 1;
}
this.guesses.push(w);
this.nGuesses++;
@ -130,13 +136,39 @@ class Wordle {
this.guessColors[this.nGuesses].push(col);
}
if (correct==5) {
E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){load();});
return 1;
}
if (this.nGuesses==5) {
E.showAlert("The word was\n"+this.word, "You lost!").then(function(){load();});
E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){
wordle.stats['p']++; wordle.stats['w']++; wordle.stats['s']++; wordle.stats[wordle.nGuesses+1]++;
if (wordle.stats['s']>wordle.stats['ms']) wordle.stats['ms'] = wordle.stats['s'];
require("Storage").writeJSON("bordlestats.json", wordle.stats);
wordle.drawStats();
});
return 2;
}
if (this.nGuesses==5) {
E.showAlert("The word was\n"+this.word, "You lost!").then(function(){
wordle.stats['p']++; wordle.stats['s'] = 0;
require("Storage").writeJSON("bordlestats.json", wordle.stats);
wordle.drawStats();
});
return 3;
}
}
drawStats() {
E.showMessage(" ", "Statistics");
var max = 1;
for (i=1; i<=6; ++i) if (max<this.stats[i]) max = this.stats[i];
var h = g.getHeight();
var w = g.getWidth();
g.setColor('#00f').setFontVector((h-40)/8).setFontAlign(-1, 0, 0);
for (i=1; i<=6; ++i) {
tw = this.stats[i]*(w-24)/max;
g.setColor("#00f").fillRect(20, 52+(i-1)*(h-52)/6+2, 20+tw, 52+i*(h-52)/6-2);
g.setColor("#fff").drawString(i.toString(), 1, 52+(i-0.5)*(h-52)/6);
g.drawString(this.stats[i].toString(), tw>20 ? 25 : 25+tw, 52+(i-0.5)*(h-52)/6);
}
g.setFontVector((h-40)/9).setColor("#fff").drawString("P:"+this.stats["p"]+" W:"+this.stats["w"]+" S:"+this.stats["s"]+" M:"+this.stats["ms"], 4, 34);
Bangle.setUI();
Bangle.on("touch", (e) => { load(); });
}
}

View File

@ -2,7 +2,7 @@
"name": "Bordle",
"shortName":"Bordle",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",

View File

@ -8,6 +8,7 @@
"screenshots": [{"url":"screenshot_calculator.png"}],
"tags": "app,tool",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"calculator.app.js","url":"app.js"},
{"name":"calculator.img","url":"calculator-icon.js","evaluate":true}

View File

@ -9,6 +9,7 @@
"type": "clock",
"tags": "clock,cli,command,bash,shell",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"cliock.app.js","url":"app.js"},

View File

@ -3,3 +3,4 @@
0.03: fix metadata.json to allow setting as clock
0.04: added heart rate which is switched on when cycled to it through up/down touch on rhs
0.05: changed text to uppercase, just looks better, removed colons on text
0.06: better contrast for light theme, use fg color instead of dithered for ring

View File

@ -28,5 +28,6 @@ See [#1248](https://github.com/espruino/BangleApps/issues/1248)
## Screenshots
![](screenshot_daisy1.png)
![](screenshot_daisy3.png)
It is worth looking at the real thing though as the screenshot does not do it justice.
It is worth looking at the real thing though as the screenshots do not do it justice.

View File

@ -41,10 +41,17 @@ Graphics.prototype.setFontRoboto20 = function(scale) {
};
function assignPalettes() {
// palette for 0-40%
pal1 = new Uint16Array([g.theme.bg, g.toColor(settings.gy), g.toColor(settings.fg), g.toColor("#00f")]);
// palette for 50-100%
pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.toColor(settings.gy), g.toColor("#00f")]);
if (g.theme.dark) {
// palette for 0-40%
pal1 = new Uint16Array([g.theme.bg, g.toColor(settings.gy), g.toColor(settings.fg), g.toColor("#00f")]);
// palette for 50-100%
pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.toColor(settings.gy), g.toColor("#00f")]);
} else {
// palette for 0-40%
pal1 = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor(settings.fg), g.toColor("#00f")]);
// palette for 50-100%
pal2 = new Uint16Array([g.theme.bg, g.toColor(settings.fg), g.theme.fg, g.toColor("#00f")]);
}
}
function setSmallFont20() {
@ -109,10 +116,10 @@ function updateSunRiseSunSet(now, lat, lon, line){
const infoData = {
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
ID_SR: { calc: () => 'Sunrise ' + sunRise },
ID_SS: { calc: () => 'Sunset ' + sunSet },
ID_STEP: { calc: () => 'Steps ' + getSteps() },
ID_BATT: { calc: () => 'Battery ' + E.getBattery() + '%' },
ID_SR: { calc: () => 'SUNRISE ' + sunRise },
ID_SS: { calc: () => 'SUNSET ' + sunSet },
ID_STEP: { calc: () => 'STEPS ' + getSteps() },
ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' },
ID_HRM: { calc: () => hrmCurrent }
};
@ -225,7 +232,7 @@ function drawSteps() {
setSmallFont();
g.setFontAlign(0,0);
g.setColor(g.theme.fg);
g.drawString('Steps ' + getSteps(), w/2, (3*h/4) - 4);
g.drawString('STEPS ' + getSteps(), w/2, (3*h/4) - 4);
drawingSteps = false;
}

View File

@ -1,13 +1,13 @@
{ "id": "daisy",
"name": "Daisy",
"version":"0.05",
"version":"0.06",
"dependencies": {"mylocation":"app"},
"description": "A clock based on the Pastel clock with large ring guage for steps",
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"screenshot_daisy2.jpg"}],
"screenshots": [{"url":"screenshot_daisy3.png"}],
"readme": "README.md",
"storage": [
{"name":"daisy.app.js","url":"app.js"},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,9 +1,9 @@
{
"id": "doztime",
"name": "Dozenal Time",
"shortName": "Dozenal Time",
"name": "Dozenal Digital Time",
"shortName": "Dozenal Digital",
"version": "0.05",
"description": "A dozenal Holocene calendar and dozenal diurnal clock",
"description": "A dozenal Holocene calendar and dozenal diurnal digital clock",
"icon": "app.png",
"type": "clock",
"tags": "clock",

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"ffcniftyb.app.js","url":"app.js"},

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"floralclk.app.js","url":"app.js"},

View File

@ -4,4 +4,6 @@
0.04: Bug fix score reset after Game Over, new icon
0.05: Chevron marker on the randomly added square
0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw
0.07: Optimized the mover algorithm for efficiency (work in progress)
0.07: Optimized the mover algorithm for efficiency (work in progress)
0.08: Bug fix at end of the game with victorious splash and glorious orchestra
0.09: Added settings menu, removed symbol selection button (*), added highscore reset

View File

@ -1,7 +1,21 @@
const debugMode = 'off'; // valid values are: off, test, production, development
let settings = Object.assign({
// default values
maxUndoLevels: 4,
charIndex: 0,
clockMode: true,
debugMode: false,
}, require('Storage').readJSON("game1024.settings.json", true) || {});
const clockMode = settings.clockMode!==undefined ? settings.clockMode : true;
const debugMode = settings.debugMode!==undefined ? settings.debugMode : false; // #settings -- valid values are: true or false
const maxUndoLevels = settings.maxUndoLevels!==undefined ? settings.maxUndoLevels : 4; // #settings
const charIndex = settings.charIndex!==undefined ? settings.charIndex : 0; // #settings -- plain numbers on the grid
delete settings; // remove unneeded settings from memory
const middle = {x:Math.floor(g.getWidth()/2)-20, y: Math.floor(g.getHeight()/2)};
const rows = 4, cols = 4;
const borderWidth = 6;
const rows = 4, cols = 4; // #settings
const borderWidth = 6;
const sqWidth = (Math.floor(Bangle.appRect.w - 48) / rows) - borderWidth;
const cellColors = [{bg:'#00FFFF', fg: '#000000'},
{bg:'#FF00FF', fg: '#000000'}, {bg:'#808000', fg: '#FFFFFF'}, {bg:'#0000FF', fg: '#FFFFFF'}, {bg:'#008000', fg: '#FFFFFF'},
@ -13,12 +27,8 @@ const cellChars = [
['0','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
['0','I', 'II', 'III', 'IV', 'V', 'VI', 'VII','VIII', 'IX', 'X']
];
// const numInitialCells = 2;
const maxUndoLevels = 4;
const noExceptions = true;
let charIndex = 0; // plain numbers on the grid
const themeBg = g.theme.bg;
const themeBg = g.theme.bg;
const scores = {
currentScore: 0,
@ -78,12 +88,12 @@ const snapshot = {
updCounter: function() {
this.counter = ++this.counter > this.interval ? 0 : this.counter;
},
dump: {gridsize: rows * cols, expVals: [], score: 0, highScore: 0, charIndex: charIndex},
dump: {gridsize: rows * cols, expVals: [], score: 0, highScore: 0},
write: function() {
require("Storage").writeJSON(this.snFileName, this.dump);
},
read: function () {
let sn = require("Storage").readJSON(this.snFileName, noExceptions);
let sn = require("Storage").readJSON(this.snFileName, true);
if ((typeof sn == "undefined") || (sn.gridsize !== rows * cols)) {
require("Storage").writeJSON(this.snFileName, this.dump);
return false;
@ -101,7 +111,6 @@ const snapshot = {
});
this.dump.score = scores.currentScore;
this.dump.highScore = scores.highScore;
this.dump.charIndex = charIndex;
},
make: function () {
this.updCounter();
@ -118,7 +127,7 @@ const snapshot = {
});
scores.currentScore = this.dump.score ? this.dump.score : 0;
scores.highScore = this.dump.highScore ? this.dump.highScore : 0 ;
charIndex = this.dump.charIndex ? this.dump.charIndex : 0 ;
if (this.dump.hasOwnProperty('charIndex')) delete this.dump.charIndex; // depricated in v0.09
}
},
reset: function () {
@ -129,12 +138,11 @@ const snapshot = {
}
this.dump.score = 0;
this.dump.highScore = scores.highScore;
this.dump.charIndex = charIndex;
this.write();
debug(() => console.log("reset D U M P E D!", this.dump));
}
};
const btnAtribs = {x: 134, w: 42, h: 42, fg:'#C0C0C0', bg:'#800000'};
const btnAtribs = {x: 134, w: 42, h: 50, fg:'#C0C0C0', bg:'#800000'};
const buttons = {
all: [],
draw: function () {
@ -162,6 +170,7 @@ const buttons = {
*/
const mover = {
gameWon: false,
direction: {
up: {name: 'up', step: 1, innerBegin: 0, innerEnd: rows-1, outerBegin: 0, outerEnd: cols-1, iter: rows -1,
sqIndex: function (i,o) {return i*(cols) + o;}, sqNextIndex: function (i,o) {return i < rows -1 ? (i+1)*(cols) + o : -1;}
@ -313,7 +322,7 @@ class Cell {
}
drawBg() {
debug(()=>console.log("Drawbg!!"));
if (this.isRndm == true) {
if (this.isRndm) {
debug(()=>console.log('Random: (ax)', this.ax));
g.setColor(this.getColor(this.expVal).bg)
.fillRect(this.x0, this.y0, this.x1, this.y1)
@ -364,7 +373,7 @@ class Cell {
this.isRndm = true;
}
drawRndmIndicator(){
if (this.isRndm == true) {
if (this.isRndm) {
debug(()=>console.log('Random: (ax)', this.ax));
g.setColor(this.getColor(0).bg)
.fillPoly(this.ax,this.ay,this.bx,this.by,this.cx,this.cy);
@ -373,8 +382,9 @@ class Cell {
}
function undoGame() {
g.clear();
if (scores.lastScores.length > 0) {
if (scores.lastScores.length) {
g.clear();
allSquares.forEach(sq => {
sq.popFromUndo();
sq.drawBg();
@ -385,9 +395,9 @@ function undoGame() {
buttons.draw();
updUndoLvlIndex();
snapshot.make();
Bangle.loadWidgets();
Bangle.drawWidgets();
}
Bangle.loadWidgets();
Bangle.drawWidgets();
}
function addToUndo() {
allSquares.forEach(sq => {
@ -397,7 +407,7 @@ function addToUndo() {
}
function addToScore (val) {
scores.add(val);
if (val == 10) messageYouWin();
if (val == 10) mover.gameWon = true;
}
function createGrid () {
let cn =0;
@ -421,15 +431,30 @@ function messageGameOver () {
.drawString("O V E R !", middle.x+12, middle.y+25);
}
function messageYouWin () {
g.setColor("#1a0d00")
const c = (g.theme.dark) ? {"fg": "#FFFFFF", "bg": "#808080"} : {"fg": "#FF0000", "bg": "#000000"};
g.setColor(c.bg)
.setFont12x20(2)
.setFontAlign(0,0,0)
.drawString("YOU HAVE", middle.x+18, middle.y-24)
.drawString("W O N ! !", middle.x+18, middle.y+24);
g.setColor("#FF0808")
g.setColor(c.fg)
.drawString("YOU HAVE", middle.x+17, middle.y-25)
.drawString("W O N ! !", middle.x+17, middle.y+25);
Bangle.buzz(200, 1);
for (let r=0;r<4;r++){
Bangle.buzz(200,0.2)
.then((result) => {
Bangle.buzz(200,0.5)
.then((result)=>{
Bangle.buzz(200,0.8)
.then((result)=>{
Bangle.buzz(200,1)
.then((result)=>{
Bangle.buzz(500,0);
})
})
})
})
}
}
function makeRandomNumber () {
return Math.ceil(2*Math.random());
@ -471,8 +496,8 @@ function initGame() {
drawGrid();
scores.draw();
buttons.draw();
// Clock mode allows short-press on button to exit
Bangle.setUI("clock");
// #settings Clock mode allows short-press on button to exit
if(clockMode) Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -491,8 +516,8 @@ function drawPopUp(message,cb) {
rDims.x+10, rDims.y2-40
]);
buttons.all.forEach(btn => {btn.disable();});
const btnYes = new Button('yes', rDims.x+16, rDims.y2-80, 54, btnAtribs.h, 'YES', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnNo = new Button('no', rDims.x2-80, rDims.y2-80, 54, btnAtribs.h, 'NO', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnYes = new Button('yes', rDims.x+16, rDims.y2-88, 54, btnAtribs.h, 'YES', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnNo = new Button('no', rDims.x2-80, rDims.y2-88, 54, btnAtribs.h, 'NO', btnAtribs.fg, btnAtribs.bg, cb, true);
btnYes.draw();
btnNo.draw();
g.setColor('#000000');
@ -527,6 +552,7 @@ function handlePopUpClicks(btn) {
function resetGame() {
g.clear();
scores.reset();
mover.gameWon=false;
allSquares.forEach(sq => {sq.setExpVal(0);sq.removeUndo();sq.setRndmFalse();});
addRandomNumber();
addRandomNumber();
@ -543,14 +569,8 @@ function resetGame() {
* @param {function} func function to call like console.log()
*/
const debug = (func) => {
switch (debugMode) {
case "development":
if (typeof func === 'function') {
func();
}
break;
case "off":
default: break;
if (debugMode) {
if (typeof func === 'function') func();
}
};
@ -653,6 +673,12 @@ function runGame(dir){
debug(() => console.log("G A M E O V E R !!"));
snapshot.reset();
messageGameOver();
} else {
if (mover.gameWon) {
debug(() => console.log("Y O U H A V E W O N !!"));
snapshot.reset();
messageYouWin();
}
}
}
@ -667,13 +693,9 @@ function updUndoLvlIndex() {
.drawString(scores.lastScores.length, x, y);
}
}
function incrCharIndex() {
charIndex++;
if (charIndex >= cellChars.length) charIndex = 0;
drawGrid();
}
buttons.add(new Button('undo', btnAtribs.x, 25, btnAtribs.w, btnAtribs.h, 'U', btnAtribs.fg, btnAtribs.bg, undoGame, true));
buttons.add(new Button('chars', btnAtribs.x, 71, btnAtribs.w, 31, '*', btnAtribs.fg, btnAtribs.bg, function(){incrCharIndex();}, true));
buttons.add(new Button('restart', btnAtribs.x, 106, btnAtribs.w, btnAtribs.h, 'R', btnAtribs.fg, btnAtribs.bg, function(){drawPopUp('Do you want\nto restart?',handlePopUpClicks);}, true));
initGame();

View File

@ -1,7 +1,7 @@
{ "id": "game1024",
"name": "1024 Game",
"shortName" : "1024 Game",
"version": "0.07",
"version": "0.09",
"icon": "game1024.png",
"screenshots": [ {"url":"screenshot.png" } ],
"readme":"README.md",
@ -12,6 +12,7 @@
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"game1024.app.js","url":"app.js"},
{"name":"game1024.settings.js","url":"settings.js"},
{"name":"game1024.img","url":"app-icon.js","evaluate":true}
]
}

70
apps/game1024/settings.js Normal file
View File

@ -0,0 +1,70 @@
(function(back) {
var FILE = "game1024.settings.json";
var scoreFile = "game1024.json";
// Load settings
var settings = Object.assign({
maxUndoLevels: 5,
charIndex: 0,
clockMode: true,
debugMode: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
var symbols = ["1 2 3 ...", "A B C ...", "I II III..."];
var settingsMenu = {
"" : { "title" : "1024 Game" },
"< Back" : () => back(),
"Symbols": {
value: 0|settings.charIndex,
min:0,max:symbols.length-1,
format: v=>symbols[v],
onchange: v=> { settings.charIndex=v; writeSettings();}
}
,
"Undo levels:": {
value: 0|settings.maxUndoLevels, // 0| converts undefined to 0
min: 0, max: 9,
onchange: v => {
settings.maxUndoLevels = v;
writeSettings();
}
},
"Exit press:": {
value: !settings.debugMode, // ! converts undefined to true
format: v => v?"short":"long",
onchange: v => {
settings.debugMode = v;
writeSettings();
},
},
"Debug mode:": {
value: !!settings.debugMode, // !! converts undefined to false
format: v => v?"On":"Off",
onchange: v => {
settings.debugMode = v;
writeSettings();
}
},
"Reset Highscore": () => {
E.showPrompt('Reset Highscore?').then((v) => {
let delay = 50;
if (v) {
delay = 500;
let sF = require("Storage").readJSON(scoreFile, true);
if (typeof sF !== "undefined") {
E.showMessage('Resetting');
sF.highScore = 0;
require("Storage").writeJSON(scoreFile, sF);
} else {
E.showMessage('No highscore!');
}
}
setTimeout(() => E.showMenu(settingsMenu), delay);
});
}
}
// Show the menu
E.showMenu(settingsMenu);
})

View File

@ -8,6 +8,7 @@
"tags": "clock",
"screenshots": [{"url":"bangle1-high-contrast-clock-screenshot.png"}],
"supports": ["BANGLEJS"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"hcclock.app.js","url":"hcclock.app.js"},

View File

@ -7,6 +7,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"screenshots": [{"url":"bangle1-impercise-word-clock-screenshot.png"}],
"allow_emulator": true,
"storage": [

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"intclock.app.js","url":"app.js"},

View File

@ -7,6 +7,7 @@
"icon": "intervals.png",
"tags": "",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"intervals.app.js","url":"intervals.app.js"},
{"name":"intervals.img","url":"intervals-icon.js","evaluate":true}

View File

@ -7,6 +7,7 @@
"tags": "tool,system,ios,apple,messages,notifications",
"dependencies": {"messages":"app"},
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"ios.app.js","url":"app.js"},
{"name":"ios.img","url":"app-icon.js","evaluate":true},

View File

@ -41,3 +41,5 @@
0.26: Setting to auto-open music
0.27: Add 'mark all read' option to popup menu (fix #1624)
0.28: Option to auto-unlock the watch when a new message arrives
0.29: Fix message list overwrites on Bangle.js 1 (fix #1642)
0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel)

View File

@ -82,31 +82,45 @@ function getNegImage() {
return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA=");
}
/*
* icons should be 24x24px with 1bpp colors and transparancy
* icons should be 24x24px with 1bpp colors and 'Transparency to Color'
* http://www.espruino.com/Image+Converter
*/
function getMessageImage(msg) {
if (msg.img) return atob(msg.img);
var s = (msg.src||"").toLowerCase();
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA");
if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
if (s=="corona-warn") return atob("GBgBAAAAABwAAP+AAf/gA//wB/PwD/PgDzvAHzuAP8EAP8AAPAAAPMAAP8AAH8AAHzsADzuAB/PAB/PgA//wAP/gAH+AAAwAAAAA");
if (s=="discord") return atob("GBgBAAAAAAAAAAAAAIEABwDgDP8wH//4H//4P//8P//8P//8Pjx8fhh+fzz+f//+f//+e//ePH48HwD4AgBAAAAAAAAAAAAAAAAA");
if (s=="facebook") return getFBIcon();
if (s=="gmail") return getNotificationImage();
if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA==");
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA=");
if (s=="instagram") return atob("GBiBAAAAAAAAAAAAAAAAAAP/wAYAYAwAMAgAkAh+EAjDEAiBEAiBEAiBEAiBEAjDEAh+EAgAEAwAMAYAYAP/wAAAAAAAAAAAAAAAAA==");
if (s=="gmail") return getNotificationImage();
if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA==");
if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA");
if (s=="lieferando") return atob("GBgBABgAAH5wAP9wAf/4A//4B//4D//4H//4P/88fV8+fV4//V4//Vw/HVw4HVw4HBg4HBg4HBg4HDg4Hjw4Hj84Hj44Hj44Hj44");
if (s=="mail") return getNotificationImage();
if (s=="messenger") return getFBIcon();
if (s=="outlook mail") return getNotificationImage();
if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA");
if (s=="outlook mail") return atob("HBwBAAAAAAAAAAAIAAAfwAAP/gAB/+AAP/5/A//v/D/+/8P/7/g+Pv8Dye/gPd74w5znHDnOB8Oc4Pw8nv/Dwe/8Pj7/w//v/D/+/8P/7/gf/gAA/+AAAfwAAACAAAAAAAAAAAA=");
if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA=");
if (s=="post & dhl") return atob("GBgBAPgAE/5wMwZ8NgN8NgP4NgP4HgP4HgPwDwfgD//AB/+AAf8AAAAABs7AHcdgG4MwAAAAGESAFESAEkSAEnyAEkSAFESAGETw");
if (s=="signal") return atob("GBgBAAAAAGwAAQGAAhggCP8QE//AB//oJ//kL//wD//0D//wT//wD//wL//0J//kB//oA//ICf8ABfxgBYBAADoABMAABAAAAAAA");
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA==");
if (s=="sms message") return getNotificationImage();
if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
if (s=="snapchat") return atob("GBgBAAAAAAAAAH4AAf+AAf+AA//AA//AA//AA//AA//AH//4D//wB//gA//AB//gD//wH//4f//+P//8D//wAf+AAH4AAAAAAAAA");
if (s=="teams") return atob("GBgBAAAAAAAAAAQAAB4AAD8IAA8cP/M+f/scf/gIeDgAfvvefvvffvvffvvffvvff/vff/veP/PeAA/cAH/AAD+AAD8AAAQAAAAA");
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
if (s=="threema") return atob("GBjB/4Yx//8AAAAAAAAAAAAAfgAB/4AD/8AH/+AH/+AP//AP2/APw/APw/AHw+AH/+AH/8AH/4AH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
if (s=="to do") return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA");
if (s=="twitch") return atob("GBgBH//+P//+P//+eAAGeAAGeAAGeDGGeDOGeDOGeDOGeDOGeDOGeDOGeAAOeAAOeAAcf4/4f5/wf7/gf//Af/+AA/AAA+AAAcAA");
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql");
if (s=="youtube") return atob("GBgBAAAAAAAAAAAAAAAAAf8AH//4P//4P//8P//8P5/8P4/8f4P8f4P8P4/8P5/8P//8P//8P//4H//4Af8AAAAAAAAAAAAAAAAA");
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
if (msg.id=="back") return getBackImage();
return getNotificationImage();
@ -115,28 +129,38 @@ function getMessageImageCol(msg,def) {
return {
// generic colors, using B2-safe colors
"alarm": "#fff",
"calendar": "#f00",
"mail": "#ff0",
"music": "#f0f",
"phone": "#0f0",
"sms message": "#0ff",
// brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos)
// all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?)
"bibel": "#54342c",
"discord": "#738adb",
"facebook": "#4267b2",
"gmail": "#ea4335",
"google home": "#fbbc05",
"home assistant": "#fff", // ha-blue is #41bdf5, but that's the background
"hangouts": "#1ba261",
"home assistant": "#fff", // ha-blue is #41bdf5, but that's the background
"instagram": "#dd2a7b",
"liferando": "#ee5c00",
"messenger": "#0078ff",
"nina": "#e57004",
"outlook mail": "#0072c6",
"post & dhl": "#f2c101",
"signal": "#00f",
"skype": "#00aff0",
"slack": "#e51670",
"threema": "#000",
"snapchat": "#ff0",
"teams": "#464eb8",
"telegram": "#0088cc",
"threema": "#000",
"to do": "#3999e5",
"twitch": "#6441A4",
"twitter": "#1da1f2",
"whatsapp": "#4fce5d",
"wordfeud": "#e7d3c7",
"youtube": "#f00",
}[(msg.src||"").toLowerCase()]||(def !== undefined?def:g.theme.fg);
}
@ -457,6 +481,7 @@ function checkMessages(options) {
var msg = MESSAGES[idx-1];
if (msg && msg.new) g.setBgColor(g.theme.bgH).setColor(g.theme.fgH);
else g.setColor(g.theme.fg);
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
if (idx==0) msg = {id:"back", title:"< Back"};
if (!msg) return;
var x = r.x+2, title = msg.title, body = msg.body;

View File

@ -1,7 +1,7 @@
{
"id": "messages",
"name": "Messages",
"version": "0.28",
"version": "0.30",
"description": "App to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "numerals,clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"bangle1-numerals-screenshot.png"}],
"storage": [

View File

@ -7,6 +7,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"readme": "README.md",
"allow_emulator": true,
"screenshots": [{"url":"bangle1-pipboy-themed-clock-screenshot.png"}],
"storage": [

View File

@ -7,6 +7,7 @@
"type": "boot",
"tags": "system",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"screenshots": [{"url":"pro-menu-screenshot.png"}],
"storage": [
{"name":"promenu.boot.js","url":"boot.js","supports": ["BANGLEJS"]},

View File

@ -10,4 +10,5 @@
0.09: Fix broken start/stop if recording not enabled (fix #1561)
0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578)
0.11: Notifications fixes
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)

View File

@ -14,7 +14,8 @@ the red `STOP` in the bottom right turns to a green `RUN`.
shown will increase, even if you are standing still.
* `TIME` - the elapsed time for your run
* `PACE` - the number of minutes it takes you to run a given distance, configured in settings (default 1km) **based on your run so far**
* `HEART` - Your heart rate
* `HEART (BPM)` - Your current heart rate
* `Max BPM` - Your maximum heart rate reached during the run
* `STEPS` - Steps since you started exercising
* `CADENCE` - Steps per second based on your step rate *over the last minute*
* `GPS` - this is green if you have a GPS lock. GPS is turned on automatically
@ -35,7 +36,7 @@ Under `Settings` -> `App` -> `Run` you can change settings for this app.
record GPS/HRM/etc data every time you start a run?
* `Pace` is the distance that pace should be shown over - 1km, 1 mile, 1/2 Marathon or 1 Marathon
* `Boxes` leads to a submenu where you can configure what is shown in each of the 6 boxes on the display.
Available stats are "Time", "Distance", "Steps", "Heart (BPM)", "Pace (avg)", "Pace (curr)", "Speed", and "Cadence".
Available stats are "Time", "Distance", "Steps", "Heart (BPM)", "Max BPM", "Pace (avg)", "Pace (curr)", "Speed", and "Cadence".
Any box set to "-" will display no information.
* Box 1 is the top left (defaults to "Distance")
* Box 2 is the top right (defaults to "Time")

View File

@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
"version":"0.12",
"version":"0.13",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",

View File

@ -42,11 +42,6 @@
value: Math.max(statsIDs.indexOf(settings[boxID]),0),
format: v => statsList[v].name,
onchange: v => {
for (var i=1;i<=6;i++)
if (settings["B"+i]==statsIDs[v]) {
settings["B"+i]="";
boxMenu["Box "+i].value=0;
}
settings[boxID] = statsIDs[v];
saveSettings();
},

View File

@ -8,6 +8,7 @@
"type": "app",
"tags": "",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"score.app.js","url":"score.app.js"},
{"name":"score.settings.js","url":"score.settings.js"},

View File

@ -7,6 +7,7 @@
"icon": "app.png",
"tags": "tool",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"showimg.app.js","url":"app.js"},
{"name":"showimg.img","url":"app-icon.js","evaluate":true}

View File

@ -4,7 +4,7 @@
"shortName": "MonoClock",
"icon": "app.png",
"screenshots": [{ "url": "screenshot0.png" }, {"url": "screenshot1.png" }],
"version": "0.04",
"version": "0.06",
"description": "A simple watchface based on my stylised monogram.",
"type": "clock",
"tags": "clock",

View File

@ -0,0 +1 @@
1.0: Initial release on the app repository for Bangle.js 1 and 2

View File

@ -0,0 +1,23 @@
# Stardate Clock
A clock face displaying a stardate along with a "standard" digital/analog clock
in LCARS design.
That design has been made popular by various Star Trek shows. Credits for the
original LCARS designs go to Michael Okuda, copyrights are owned by Paramount Global,
usage of that type of design is permitted freely for non-profit use cases.
The Bangle.js version has been created by Robert Kaiser <kairo@kairo.at>.
The stardate concept used leans on the shows released from the late 80s onward
by using 1000 units per Earth year, but to apply this more cleanly, this split
is applied exactly. Also, to give more relationship to the shows and
incidentally make values look similar to those depicted there, the zero point
is set to the first airing of the original 'Star Trek' series in the US on
Thursday, September 8, 1966, at 8:30 p.m. Eastern Time.
The clock face supports Bangle.js 1 and 2 with some compromises (e.g. the
colors will look best on Bangle.js 1, the font sizes will look best on
Bangle.js 2).
Any tap on the diaply while unlocked switches the "standard" Earth-style clock
between digital and analog display.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgkiAA0gDRwX/C/4X/C5MO9wBDgnkAIMAAQQXKAItehwECAQIXK8gBEIQIeDC5YAF8EAAIIECC48hE4oYCAogXIkQvHDIgCBiQXHiCPFAIaaCgECJBChDAIsOU4RIJbJwwIDIVEABIYBMJAXOAC8SmYAHmHdABJfBCxAXNCpEyRxoWVgETC46+OkYXHRpxGWC5EwBQMBkQDBiK4DKQMBiAXKNQMggQ2CgI7CkcgC5UjicwkYXCgUxmakBC5kCmERC4MiAoMjgMTC50AC4KYCkcAgYXRPgJFBC6YABgYEBC6iQBC6cRgMgL6ikBR4IXOiR3EX4IXPAgTXDBgIXNgUiiClCAAMikIKBC5YAMC64AXogAGoAX/C6w"))

362
apps/stardateclock/app.js Normal file
View File

@ -0,0 +1,362 @@
// Stardate clock face, by KaiRo.at, 2021-2022
var redrawClock = true;
var clockface = "digital";
// note: Bangle.js 1 has 240x240x16, 2 has 176x176x3 screen
var bpp = g.getBPP ? g.getBPP() : 16;
// Load fonts
Graphics.prototype.setFontAntonio27 = function(scale) {
// Actual height 23 (23 - 1)
g.setFontCustom(atob("AAAAAAGAAAAwAAAGAAAAwAAAGAAAAwAAAAAAAAAAAAAAAAAADAAAA4AAAHAAAAAAAAAAAAAAAAAAAAAA4AAB/AAD/4AH/4AP/wAf/gAD/AAAeAAAAAAAAAAAAA///AP//+D///4eAAPDgAA4cAAHD///4P//+A///gAAAAAAAAAAAAAAYAAAHAAAA4AAAOAAAD///4f///D///4AAAAAAAAAAAAAAAAAAAAAAA/gD4P8B/D/g/4cAfzDgP4Yf/8DD/+AYP/ADAGAAAAAAAAAAAAHwD8B+AfwfwD/DgMA4cDgHDgeA4f///B/3/wH8P8AAAAAAAAAAAAOAAAPwAAP+AAP/wAf8OAf4BwD///4f///D///4AABwAAAGAAAAAAAAAAAAAAD/4Pwf/h/D/4P4cMAHDjgA4cf//Dh//4cH/8AAAAAAAAAAAAAAH//8B///wf///Dg4A4cHAHDg4A4f3//B+f/wHh/8AAAAAAAAAAAAAAcAAADgAA4cAD/DgH/4cH//Dv/4Af/gAD/gAAfAAADgAAAAAAAAAAAAH4f8B///wf///Dg8A4cDAHDg8A4f///B///wH8/8AAAAAAAAAAAAAAH/h4B/+Pwf/5/DgHA4cA4HDgHA4f///B///wH//8AAAAAAAAAAAAAAAAAAAHgeAA8DwAHgeAAAAAAAAAA"), 45, atob("CQcKDAsMDAwMDAwMDAc="), 27+(scale<<8)+(1<<16));
};
Graphics.prototype.setFontAntonio42 = function(scale) {
// Actual height 36 (36 - 1)
g.setFontCustom(atob("AAAAAAAAAAAAAAAAAcAAAAAAcAAAAAAcAAAAAAcAAAAAAcAAAAAAcAAAAAAcAAAAAAcAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAAAAHgAAAAAHgAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAfgAAAAH/gAAAB//gAAAf//gAAH//4AAB//+AAAf//gAAH//4AAAf/+AAAAf/gAAAAf4AAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAA////gAH////+AP/////Af/////gf/////gfAAAAPgeAAAAHgeAAAAHgfAAAAPgf/////gf/////gP/////AH////+AB////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAB4AAAAAB4AAAAADwAAAAAHwAAAAAP/////gf/////gf/////gf/////gf/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAPgH/8AD/gP/8AP/gP/8A//gf/8B//gfAAH/ngeAAf+HgeAB/4HgfAH/gHgf//+AHgP//4AHgH//wAHgD/+AAHgAPgAAAAAAAAAAAAAAAAAAAAAAAAAA+AAfwAH+AAf+AP+AAf/AP+AAf/Af+AAf/gfADwAPgeADwAHgeADwAHgfAH4APgf///h/gf/////AP/+///AH/+f/+AB/4H/4AAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA/gAAAAH/gAAAB//gAAAP//gAAB//HgAAf/wHgAD/8AHgAf/AAHgAf/////gf/////gf/////gf/////gf/////gAAAAHgAAAAAHgAAAAAHAAAAAAAAAAAAAAAAAAAAAAAf//gP8Af//gP+Af//gP/Af//gP/gf/+AAfgeB8AAHgeB4AAHgeB8AAHgeB////geB////geA////AeAf//+AAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///gAD////8AH/////AP/////Af/////gfAPgAfgeAPAAHgeAPAAHgeAPAAHgf+PgAPgf+P///gP+H///AH+H//+AB+B//8AAAAD8AAAAAAAAAAAAAAAAAAAAAAAeAAAAAAeAAAAAAeAAAAPgeAAAP/geAAD//geAA///geAH///geB///+AeP//4AAe//8AAAf//AAAAf/wAAAAf+AAAAAfwAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wH/4AH/8f/+AP/////Af/////gf/////geAH4APgeADgAHgeADgAHgeAHwAHgf/////gf/////gP/////AH/8//+AB/wH/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//gPgAH//4P+AP//8P/Af//+P/AfwB+P/geAAeAPgeAAeAHgeAAeAHgfAAeAPgf/////gP/////AP/////AH////8AA////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4APgAAH4AfgAAH4AfgAAH4AfgAAH4AfgAAD4APgAAAAAAAAAAAAAAA="), 45, atob("DgsPEhESEhISEhISEgo="), 42+(scale<<8)+(1<<16));
};
const fontName = "Antonio27";
const fontNameLarge = "Antonio42";
const fontSize = 1;
const fontSizeLarge = 1;
const fontHeightLarge = 42 * fontSizeLarge;
// LCARS dimensions
if (g.getWidth() < 200) { // Bangle.js 2
const baseUnit1 = 3;
const baseUnit2 = 2;
const baseUnit3 = 7;
}
else {
const baseUnit1 = 5;
const baseUnit2 = 3;
const baseUnit3 = 10;
}
const widgetsHeight = 24;
const sbarWid = baseUnit3 * 5;
const hbarHt = baseUnit1;
const outRad = baseUnit1 * 5;
const inRad = outRad - hbarHt;
const gap = baseUnit2;
const divisionPos = baseUnit3 * 8;
const sbarGapPos = baseUnit3 * 15;
const lowerTop = divisionPos+gap+1;
// Star Trek famously premiered on Thursday, September 8, 1966, at 8:30 p.m.
// See http://www.startrek.com/article/what-if-the-original-star-trek-had-debuted-on-friday-nights
const gSDBase = new Date("September 8, 1966 20:30:00 EST");
const sdatePosBottom = divisionPos - hbarHt - 1;
const sdatePosRight = g.getWidth() - baseUnit2;
const sdateDecimals = 1;
const secondsPerYear = 86400 * 365.2425;
const sdateDecFactor = Math.pow(10, sdateDecimals);
const clockAreaLeft = sbarWid + inRad / 2;
const clockAreaTop = lowerTop + hbarHt + inRad / 2;
const clockWid = g.getWidth() - clockAreaLeft;
const clockHt = g.getHeight() - clockAreaTop;
const ctimePosTop = clockAreaTop + baseUnit1 * 5;
const ctimePosCenter = clockAreaLeft + clockWid / 2;
const cdatePosTop = ctimePosTop + fontHeightLarge;
const cdatePosCenter = clockAreaLeft + clockWid / 2;
const clockCtrX = Math.floor(clockAreaLeft + clockWid / 2);
const clockCtrY = Math.floor(clockAreaTop + clockHt / 2);
const analogRad = Math.floor(Math.min(clockWid, clockHt) / 2);
const analogMainLineLength = baseUnit1 * 2;
const analogSubLineLength = baseUnit1;
const analogHourHandLength = analogRad / 2;
const analogMinuteHandLength = analogRad - analogMainLineLength / 2;
const colorBg = "#000000";
const colorTime = "#9C9CFF";
const colorDate = "#A09090";
const colorStardate = "#FFCF00";
// On low-bpp devices (Bangle.js 2), use basic colors for analog clock.
const colorHours = bpp > 3 ? "#9C9CFF" : "#00FF00";
const colorSeconds = bpp > 3 ? "#E7ADE7" : "#FFFF00";
const colorHands = bpp > 3 ? "#A09090" : "#00FFFF";
const colorLCARSGray = "#A09090";
const colorLCARSOrange = "#FF9F00";
const colorLCARSPink = "#E7ADE7";
const colorLCARSPurple = "#A06060";
const colorLCARSBrown = "#C09070";
// More colors: teal #008484, yellow FFCF00, purple #6050B0
var lastSDateString;
var lastTimeStringToMin;
var lastTimeStringSec;
var lastDateString;
var lastAnalogDate;
function updateStardate() {
var curDate = new Date();
// Note that the millisecond division and the 1000-unit multiplier cancel each other out.
var sdateval = (curDate - gSDBase) / secondsPerYear;
var sdatestring = (Math.floor(sdateval * sdateDecFactor) / sdateDecFactor).toFixed(sdateDecimals);
// Reset the state of the graphics library.
g.reset();
g.setBgColor(colorBg);
// Set Font
g.setFont(fontName, fontSize);
if (lastSDateString) {
// Clear the area where we want to draw the time.
//g.setBgColor("#FF6600"); // for debugging
g.clearRect(sdatePosRight - g.stringWidth(lastSDateString) - 1,
sdatePosBottom - g.getFontHeight(),
sdatePosRight,
sdatePosBottom);
}
// Draw the current stardate.
g.setColor(colorStardate);
g.setFontAlign(1, 1, 0); // Align following string to bottom right.
g.drawString(sdatestring, sdatePosRight, sdatePosBottom);
lastSDateString = sdatestring;
// Schedule next when an update to the last decimal is due.
var mstonextUpdate = (Math.ceil(sdateval * sdateDecFactor) / sdateDecFactor - sdateval) * secondsPerYear;
if (redrawClock) {
setTimeout(updateStardate, mstonextUpdate);
}
}
function updateConventionalTime() {
var curDate = new Date();
if (clockface == "digital") {
drawDigitalClock(curDate);
}
else {
drawAnalogClock(curDate);
}
// Schedule next when an update to the last second is due.
var mstonextUpdate = Math.ceil(curDate / 1000) * 1000 - curDate;
if (redrawClock) {
setTimeout(updateConventionalTime, mstonextUpdate);
}
}
function drawDigitalClock(curDate) {
var timestringToMin = ("0" + curDate.getHours()).substr(-2) + ":"
+ ("0" + curDate.getMinutes()).substr(-2) + ":";
var timestringSec = ("0" + curDate.getSeconds()).substr(-2);
var datestring = "" + curDate.getFullYear() + "-"
+ ("0" + (curDate.getMonth() + 1)).substr(-2) + "-"
+ ("0" + curDate.getDate()).substr(-2);
// Reset the state of the graphics library.
g.reset();
g.setBgColor(colorBg);
// Set Font
g.setFont(fontNameLarge, fontSizeLarge);
var ctimePosLeft = ctimePosCenter - g.stringWidth("12:34:56") / 2;
if (ctimePosLeft + g.stringWidth("00:00:00") > g.getWidth()) {
ctimePosLeft = g.getWidth() - g.stringWidth("00:00:00");
}
g.setColor(colorTime);
if (timestringToMin != lastTimeStringToMin) {
if (lastTimeStringToMin) {
// Clear the area where we want to draw the time.
//g.setBgColor("#FF6600"); // for debugging
g.clearRect(ctimePosLeft,
ctimePosTop,
ctimePosLeft + g.stringWidth(lastTimeStringToMin) + 1,
ctimePosTop + g.getFontHeight());
}
// Draw the current time.
g.drawString(timestringToMin, ctimePosLeft, ctimePosTop);
lastTimeStringToMin = timestringToMin;
}
var ctimePosLeftSec = ctimePosLeft + g.stringWidth(timestringToMin);
if (lastTimeStringSec) {
// Clear the area where we want to draw the seconds.
//g.setBgColor("#FF6600"); // for debugging
g.clearRect(ctimePosLeftSec,
ctimePosTop,
ctimePosLeftSec + g.stringWidth(lastTimeStringSec) + 1,
ctimePosTop + g.getFontHeight());
}
// Draw the current seconds.
g.drawString(timestringSec, ctimePosLeftSec, ctimePosTop);
lastTimeStringSec = timestringSec;
if (datestring != lastDateString) {
// Set Font
g.setFont(fontName, fontSize);
var cdatePosLeft = cdatePosCenter - g.stringWidth("1234-56-78") / 2;
if (lastDateString) {
// Clear the area where we want to draw the time.
//g.setBgColor("#FF6600"); // for debugging
g.clearRect(cdatePosLeft,
cdatePosTop,
cdatePosLeft + g.stringWidth(lastDateString) + 1,
cdatePosTop + g.getFontHeight());
}
// Draw the current date.
g.setColor(colorDate);
//g.setFontAlign(0, -1, 0); // Align following string to bottom right.
g.drawString(datestring, cdatePosLeft, cdatePosTop);
lastDateString = datestring;
}
}
function drawLine(x1, y1, x2, y2, color) {
g.setColor(color);
// On high-bpp devices, use anti-aliasing. Low-bpp (Bangle.js 2) doesn't clear nicely with AA.
if (bpp > 3 && g.drawLineAA) {
g.drawLineAA(x1, y1, x2, y2);
}
else {
g.drawLine(x1, y1, x2, y2);
}
}
function clearLine(x1, y1, x2, y2) {
drawLine(x1, y1, x2, y2, colorBg);
}
function drawAnalogClock(curDate) {
// Reset the state of the graphics library.
g.reset();
g.setBgColor(colorBg);
// Init variables for drawing any seconds we have not drawn.
// If minute changed, we'll set for the full wheel below.
var firstDrawSecond = lastAnalogDate ? lastAnalogDate.getSeconds() + 1 : curDate.getSeconds();
var lastDrawSecond = curDate.getSeconds();
if (!lastAnalogDate || curDate.getMinutes() != lastAnalogDate.getMinutes()) {
// Draw the main hour lines.
//g.setColor("#9C9CFF");
//g.drawCircle(clockCtrX, clockCtrY, analogRad);
for (let i = 0; i < 60; i = i + 15) {
let edgeX = clockCtrX + analogRad * Math.sin(i * Math.PI / 30);
let edgeY = clockCtrY - analogRad * Math.cos(i * Math.PI / 30);
let innerX = clockCtrX + (analogRad - analogMainLineLength) * Math.sin(i * Math.PI / 30);
let innerY = clockCtrY - (analogRad - analogMainLineLength) * Math.cos(i * Math.PI / 30);
drawLine(edgeX, edgeY, innerX, innerY, colorHours);
}
// Set for drawing the full second wheel.
firstDrawSecond = 0;
lastDrawSecond = 59;
}
// Draw the second wheel, or the parts of it that we haven't done yet.
for (let i = firstDrawSecond; i <= lastDrawSecond; i++) {
let edgeX = clockCtrX + analogRad * Math.sin(i * Math.PI / 30);
let edgeY = clockCtrY - analogRad * Math.cos(i * Math.PI / 30);
let innerX = clockCtrX + (analogRad - analogSubLineLength) * Math.sin(i * Math.PI / 30);
let innerY = clockCtrY - (analogRad - analogSubLineLength) * Math.cos(i * Math.PI / 30);
if (i <= curDate.getSeconds()) {
drawLine(edgeX, edgeY, innerX, innerY, colorSeconds);
}
else if (i % 5 == 0) {
drawLine(edgeX, edgeY, innerX, innerY, colorHours);
}
else {
clearLine(edgeX, edgeY, innerX, innerY);
}
}
if (lastAnalogDate) {
// Clear previous hands.
if (curDate.getMinutes() != lastAnalogDate.getMinutes()) {
// Clear hour hand.
let HhAngle = (lastAnalogDate.getHours() + lastAnalogDate.getMinutes() / 60) * Math.PI / 6;
let HhEdgeX = clockCtrX + analogHourHandLength * Math.sin(HhAngle);
let HhEdgeY = clockCtrY - analogHourHandLength * Math.cos(HhAngle);
clearLine(HhEdgeX, HhEdgeY, clockCtrX, clockCtrY);
// Clear minute hand.
let MhEdgeX = clockCtrX + analogMinuteHandLength * Math.sin(lastAnalogDate.getMinutes() * Math.PI / 30);
let MhEdgeY = clockCtrY - analogMinuteHandLength * Math.cos(lastAnalogDate.getMinutes() * Math.PI / 30);
clearLine(MhEdgeX, MhEdgeY, clockCtrX, clockCtrY);
}
}
if (!lastAnalogDate || curDate.getMinutes() != lastAnalogDate.getMinutes()) {
// Draw hour hand.
let HhAngle = (curDate.getHours() + curDate.getMinutes() / 60) * Math.PI / 6;
let HhEdgeX = clockCtrX + analogHourHandLength * Math.sin(HhAngle);
let HhEdgeY = clockCtrY - analogHourHandLength * Math.cos(HhAngle);
drawLine(HhEdgeX, HhEdgeY, clockCtrX, clockCtrY, colorHands);
// Draw minute hand.
let MhEdgeX = clockCtrX + analogMinuteHandLength * Math.sin(curDate.getMinutes() * Math.PI / 30);
let MhEdgeY = clockCtrY - analogMinuteHandLength * Math.cos(curDate.getMinutes() * Math.PI / 30);
drawLine(MhEdgeX, MhEdgeY, clockCtrX, clockCtrY, colorHands);
}
lastAnalogDate = curDate;
}
function switchClockface() {
if (clockface == "digital") {
clockface = "analog";
}
else {
clockface = "digital";
}
// Clear whole lower area.
g.clearRect(clockAreaLeft,clockAreaTop,g.getWidth(),g.getHeight());
lastTimeStringToMin = undefined;
lastTimeStringSec = undefined;
lastDateString = undefined;
lastAnalogDate = undefined;
}
// Clear the screen once, at startup.
g.setBgColor(colorBg);
g.clear();
// Draw LCARS borders.
// Upper section: rounded corner.
g.setColor(colorLCARSGray);
g.fillCircle(outRad, divisionPos - outRad, outRad);
g.fillRect(outRad, divisionPos - outRad, sbarWid + inRad, divisionPos);
g.fillRect(outRad, divisionPos - hbarHt, sbarWid + outRad, divisionPos); // div bar stub
g.fillRect(0, 0, sbarWid, divisionPos - outRad); // side bar
g.setColor(colorBg); // blocked out areas of corner
g.fillCircle(sbarWid + inRad + 1, divisionPos - hbarHt - inRad - 1, inRad);
g.fillRect(sbarWid + 1, divisionPos - outRad * 2, sbarWid + outRad, divisionPos - hbarHt - inRad);
// upper division bar
g.setColor(colorLCARSPurple);
g.fillRect(sbarWid + outRad + gap + 1, divisionPos - hbarHt, g.getWidth(), divisionPos);
// Lower section: rounded corner.
g.setColor(colorLCARSPink);
g.fillCircle(outRad, lowerTop + outRad, outRad);
g.fillRect(outRad, lowerTop, sbarWid + inRad, lowerTop + outRad);
g.fillRect(outRad, lowerTop, sbarWid + outRad, lowerTop + hbarHt); // div bar stub
g.fillRect(0, lowerTop + outRad, sbarWid, sbarGapPos); // side bar
g.setColor(colorBg); // blocked out areas of corner
g.fillCircle(sbarWid + inRad + 1, lowerTop + hbarHt + inRad + 1, inRad);
g.fillRect(sbarWid + 1, lowerTop + hbarHt + inRad, sbarWid + outRad, lowerTop + outRad * 2);
// lower division bar
g.setColor(colorLCARSOrange);
g.fillRect(sbarWid + outRad + gap + 1, lowerTop, g.getWidth(), lowerTop + hbarHt);
// second color of side bar
g.setColor(colorLCARSBrown);
g.fillRect(0, sbarGapPos + gap + 1, sbarWid, g.getHeight());
// Draw immediately at first.
updateStardate();
updateConventionalTime();
// Make sure widgets can be shown.
//g.setColor("#FF0000"); g.fillRect(0, 0, g.getWidth(), widgetsHeight); // debug
Bangle.loadWidgets();
Bangle.drawWidgets();
// Show launcher on button press as usual for a clock face
Bangle.setUI("clock", Bangle.showLauncher);
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower', on => {
if (on) {
redrawClock = true;
// Draw immediately to kick things off.
updateStardate();
updateConventionalTime();
}
else {
redrawClock = false;
}
});
Bangle.on('touch', button => {
// button == 1 is left, 2 is right
switchClockface();
});

BIN
apps/stardateclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

View File

@ -0,0 +1,23 @@
{
"id": "stardateclock",
"name":"Stardate Clock",
"shortName":"Stardate Clock",
"description": "A clock displaying a stardate along with a 'standard' digital/analog clock in LCARS design",
"version":"1.0",
"icon": "app.png",
"type":"clock",
"tags": "clock",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name": "stardateclock.app.js","url": "app.js"},
{"name": "stardateclock.img","url": "app-icon.js","evaluate": true}
],
"screenshots": [
{"url": "screenshot1.png"},
{"url": "screenshot2.png"},
{"url": "screenshot3.png"},
{"url": "screenshot4.png"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -7,6 +7,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"sunclock.app.js","url":"app.js"},

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"readme": "README.md",
"storage": [
{"name":"supmariodark.app.js","url":"supmariodark.js"},
{"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true},

View File

@ -8,6 +8,7 @@
"type": "bootloader",
"tags": "tool",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"touchmenu.boot.js","url":"touchmenu.boot.js"}
]

View File

@ -8,6 +8,7 @@
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"waveclk.app.js","url":"app.js"},

View File

@ -15,6 +15,7 @@ print(ExStats.getList());
{name: "Distance", id:"dist"},
{name: "Steps", id:"step"},
{name: "Heart (BPM)", id:"bpm"},
{name: "Max BPM", id:"maxbpm"},
{name: "Pace (avr)", id:"pacea"},
{name: "Pace (current)", id:"pacec"},
{name: "Cadence", id:"caden"},
@ -72,6 +73,7 @@ var state = {
// cadence // steps per minute adjusted if <1 minute
// BPM // beats per minute
// BPMage // how many seconds was BPM set?
// maxBPM // The highest BPM reached while active
// Notifies: 0 for disabled, otherwise how often to notify in meters, seconds, or steps
notify: {
dist: {
@ -159,6 +161,10 @@ Bangle.on("HRM", function(h) {
if (h.confidence>=60) {
state.BPM = h.bpm;
state.BPMage = 0;
if (state.maxBPM < h.bpm) {
state.maxBPM = h.bpm;
if (stats["maxbpm"]) stats["maxbpm"].emit("changed",stats["maxbpm"]);
}
if (stats["bpm"]) stats["bpm"].emit("changed",stats["bpm"]);
}
});
@ -170,6 +176,7 @@ exports.getList = function() {
{name: "Distance", id:"dist"},
{name: "Steps", id:"step"},
{name: "Heart (BPM)", id:"bpm"},
{name: "Max BPM", id:"maxbpm"},
{name: "Pace (avg)", id:"pacea"},
{name: "Pace (curr)", id:"pacec"},
{name: "Speed", id:"speed"},
@ -230,6 +237,14 @@ exports.getStats = function(statIDs, options) {
getString : function() { return state.BPM||"--" },
};
}
if (statIDs.includes("maxbpm")) {
needHRM = true;
stats["maxbpm"]={
title : "Max BPM",
getValue : function() { return state.maxBPM; },
getString : function() { return state.maxBPM||"--" },
};
}
if (statIDs.includes("pacea")) {
needGPS = true;
stats["pacea"]={
@ -299,6 +314,7 @@ exports.getStats = function(statIDs, options) {
state.curSpeed = 0;
state.BPM = 0;
state.BPMage = 0;
state.maxBPM = 0;
state.notify = options.notify;
if (options.notify.dist.increment > 0) {
state.notify.dist.next = state.distance + options.notify.dist.increment;