Merge branch 'espruino:master' into weather-widget-hide

pull/1029/head
Marco H 2021-12-08 11:34:28 +01:00 committed by GitHub
commit 1c9b949e39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 661 additions and 29 deletions

View File

@ -57,7 +57,7 @@
{ {
"id": "messages", "id": "messages",
"name": "Messages", "name": "Messages",
"version": "0.09", "version": "0.10",
"description": "App to display notifications from iOS and Gadgetbridge", "description": "App to display notifications from iOS and Gadgetbridge",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",
@ -95,7 +95,7 @@
{ {
"id": "ios", "id": "ios",
"name": "iOS Integration", "name": "iOS Integration",
"version": "0.04", "version": "0.06",
"description": "(BETA) App to display notifications from iOS devices", "description": "(BETA) App to display notifications from iOS devices",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,ios,apple,messages,notifications", "tags": "tool,system,ios,apple,messages,notifications",
@ -197,7 +197,7 @@
{ {
"id": "locale", "id": "locale",
"name": "Languages", "name": "Languages",
"version": "0.11", "version": "0.12",
"description": "Translations for different countries", "description": "Translations for different countries",
"icon": "locale.png", "icon": "locale.png",
"type": "locale", "type": "locale",
@ -731,6 +731,7 @@
"description": "Application that allows you to record a GPS track. Can run in background", "description": "Application that allows you to record a GPS track. Can run in background",
"icon": "app.png", "icon": "app.png",
"tags": "tool,outdoors,gps,widget", "tags": "tool,outdoors,gps,widget",
"screenshots": [{"url":"screenshot.png"}],
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
"interface": "interface.html", "interface": "interface.html",
@ -1126,7 +1127,7 @@
{ {
"id": "qrcode", "id": "qrcode",
"name": "Custom QR Code", "name": "Custom QR Code",
"version": "0.02", "version": "0.03",
"description": "Use this to upload a customised QR code to Bangle.js", "description": "Use this to upload a customised QR code to Bangle.js",
"icon": "app.png", "icon": "app.png",
"tags": "qrcode", "tags": "qrcode",
@ -4339,7 +4340,7 @@
"version": "0.01", "version": "0.01",
"description": "A touch based GPS watch, shows OS map reference", "description": "A touch based GPS watch, shows OS map reference",
"icon": "gpstouch.png", "icon": "gpstouch.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}], "screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
"tags": "tools,app", "tags": "tools,app",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"readme": "README.md", "readme": "README.md",
@ -4780,5 +4781,25 @@
{"name": "flow.app.js", "url": "app.js" }, {"name": "flow.app.js", "url": "app.js" },
{"name": "flow.img", "url": "app-icon.js","evaluate": true } {"name": "flow.img", "url": "app-icon.js","evaluate": true }
] ]
},
{ "id": "scribble",
"name": "Scribble",
"shortName":"Scribble",
"version":"0.01",
"type": "app",
"description": "A keyboard on your wrist! Swipe right for space, left for delete.",
"icon": "app.png",
"allow_emulator": true,
"tags": "tools, keyboard, text, scribble",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"scribble.app.js","url":"app.js"},
{"name":"scribble.img","url":"app-icon.js","evaluate":true}
],
"screenshots":[
{ "url":"screenshot.png" }
]
} }
] ]

View File

@ -8,3 +8,6 @@ This app allows you to record a GPS track. It can run in background. The data ca
When you turn on recording, a widget badge that looks like a satellite will appear immediately at the top of the screen. However, the recording does not begin immediately. It usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). You will notice a blinking question mark at the lower left of the badge indicating currently getting a fix. The badge will change when a GPS fix is achieved and that is when the app actually starts writing data to the log file. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. When you turn on recording, a widget badge that looks like a satellite will appear immediately at the top of the screen. However, the recording does not begin immediately. It usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). You will notice a blinking question mark at the lower left of the badge indicating currently getting a fix. The badge will change when a GPS fix is achieved and that is when the app actually starts writing data to the log file. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix.
## Viewing a track
![](screenshot.png)

BIN
apps/gpsrec/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -14,3 +14,5 @@
![](screenshot2.png) ![](screenshot2.png)
![](screenshot3.png) ![](screenshot3.png)
![](screenshot4.png) ![](screenshot4.png)
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)

View File

@ -2,3 +2,6 @@
0.02: Remove messages on disconnect 0.02: Remove messages on disconnect
0.03: Handling of message actions (ok/clear) 0.03: Handling of message actions (ok/clear)
0.04: Added common bundleId's 0.04: Added common bundleId's
0.05: Added more bundleId's (app-id's which can be used to
determine a friendly app name in the notifications)
0.06: Fix (not) popupping up old messages

View File

@ -26,6 +26,13 @@ E.on('ANCS',msg=>{
function ancsHandler() { function ancsHandler() {
var msg = Bangle.ancsMessageQueue[0]; var msg = Bangle.ancsMessageQueue[0];
NRF.ancsGetNotificationInfo( msg.uid ).then( info => { NRF.ancsGetNotificationInfo( msg.uid ).then( info => {
if(msg.preExisting === true){
info.new = false;
} else {
info.new = true;
}
E.emit("notify", Object.assign(msg, info)); E.emit("notify", Object.assign(msg, info));
Bangle.ancsMessageQueue.shift(); Bangle.ancsMessageQueue.shift();
if (Bangle.ancsMessageQueue.length) if (Bangle.ancsMessageQueue.length)
@ -49,30 +56,47 @@ E.on('notify',msg=>{
"message" : string, "message" : string,
"messageSize" : string, "messageSize" : string,
"date" : string, "date" : string,
"new" : boolean,
"posAction" : string, "posAction" : string,
"negAction" : string, "negAction" : string,
"name" : string, "name" : string,
*/ */
var appNames = { var appNames = {
"nl.ah.Appie": "Albert Heijn", "com.apple.facetime": "FaceTime",
"com.apple.mobilecal": "Calendar", "com.apple.mobilecal": "Calendar",
"com.apple.mobilemail": "Mail", "com.apple.mobilemail": "Mail",
"com.apple.MobileSMS": "SMS Message",
"com.apple.Passbook": "iOS Wallet",
"com.apple.reminders": "Reminders", "com.apple.reminders": "Reminders",
"com.apple.shortcuts": "Shortcuts", "com.apple.shortcuts": "Shortcuts",
"com.atebits.Tweetie2": "Twitter", "com.atebits.Tweetie2": "Twitter",
"com.burbn.instagram" : "Instagram", "com.burbn.instagram" : "Instagram",
"com.facebook.Facebook": "Facebook", "com.facebook.Facebook": "Facebook",
"com.facebook.Messenger": "FB Messenger", "com.facebook.Messenger": "FB Messenger",
"com.google.Chromecast" : "Google Home",
"com.google.Gmail" : "GMail", "com.google.Gmail" : "GMail",
"com.google.hangouts" : "Hangouts", "com.google.hangouts" : "Hangouts",
"com.google.ios.youtube" : "YouTube", "com.google.ios.youtube" : "YouTube",
"com.hammerandchisel.discord" : "Discord",
"com.ifttt.ifttt" : "IFTTT",
"com.jumbo.app" : "Jumbo", "com.jumbo.app" : "Jumbo",
"com.linkedin.LinkedIn" : "LinkedIn",
"com.nestlabs.jasper.release" : "Nest",
"com.netflix.Netflix" : "Netflix", "com.netflix.Netflix" : "Netflix",
"com.reddit.Reddit" : "Reddit",
"com.skype.skype": "Skype", "com.skype.skype": "Skype",
"com.skype.SkypeForiPad": "Skype", "com.skype.SkypeForiPad": "Skype",
"com.spotify.client": "Spotify", "com.spotify.client": "Spotify",
"net.whatsapp.WhatsApp": "WhatsApp", "com.tinyspeck.chatlyio": "Slack",
"com.toyopagroup.picaboo": "Snapchat",
"com.ubercab.UberClient": "Uber",
"com.ubercab.UberEats": "UberEats",
"com.wordfeud.free": "WordFeud", "com.wordfeud.free": "WordFeud",
"com.zhiliaoapp.musically": "TikTok",
"net.whatsapp.WhatsApp": "WhatsApp",
"nl.ah.Appie": "Albert Heijn",
"nl.postnl.TrackNTrace": "PostNL",
"ph.telegra.Telegraph": "Telegram",
// could also use NRF.ancsGetAppInfo(msg.appId) here // could also use NRF.ancsGetAppInfo(msg.appId) here
}; };
@ -85,6 +109,7 @@ E.on('notify',msg=>{
t : msg.event, t : msg.event,
id : msg.uid, id : msg.uid,
src : appNames[msg.appId] || msg.appId, src : appNames[msg.appId] || msg.appId,
new : msg.new,
title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer), title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer),
subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer), subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer),
body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer)

View File

@ -11,3 +11,4 @@
0.09: Added New Zealand en_NZ 0.09: Added New Zealand en_NZ
0.10: Apply 12hour setting to time 0.10: Apply 12hour setting to time
0.11: Added translations for nl_NL and changes one formatting 0.11: Added translations for nl_NL and changes one formatting
0.12: Fixed nl_NL formatting, because the full months won't fit on the Bangle.js2's screen

View File

@ -184,7 +184,7 @@ var locales = {
temperature: "°C", temperature: "°C",
ampm: { 0: "", 1: "" }, ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" }, timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%B %d %Y", 1: "%d.%m.%y" }, // zondag 1 maart 2020 // 01.01.20 datePattern: { 0: "%d %b %Y", 1: "%d-%m-%Y" }, // 28 feb 2020 // 28-02-2020
abday: "zo,ma,di,wo,do,vr,za", abday: "zo,ma,di,wo,do,vr,za",
day: "zondag,maandag,dinsdag,woensdag,donderdag,vrijdag,zaterdag", day: "zondag,maandag,dinsdag,woensdag,donderdag,vrijdag,zaterdag",
abmonth: "jan,feb,mrt,apr,mei,jun,jul,aug,sep,okt,nov,dec", abmonth: "jan,feb,mrt,apr,mei,jun,jul,aug,sep,okt,nov,dec",

View File

@ -12,3 +12,4 @@
buzz on new message (fix #999) buzz on new message (fix #999)
0.09: Message now disappears after 60s if no action taken and clock loads (fix 922) 0.09: Message now disappears after 60s if no action taken and clock loads (fix 922)
Fix phone icon (#1014) Fix phone icon (#1014)
0.10: Respect the 'new' attribute if it was set from iOS integrations

View File

@ -1,5 +1,5 @@
/* Push a new message onto messages queue, event is: /* Push a new message onto messages queue, event is:
{t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new {t:"add",id:int, src,title,subject,body,sender,tel, important:bool, new:bool}
{t:"add",id:int, id:"music", state, artist, track, etc} // add new {t:"add",id:int, id:"music", state, artist, track, etc} // add new
{t:"remove-",id:int} // remove {t:"remove-",id:int} // remove
{t:"modify",id:int, title:string} // modified {t:"modify",id:int, title:string} // modified
@ -16,7 +16,11 @@ exports.pushMessage = function(event) {
if (mIdx>=0) messages.splice(mIdx, 1); // remove item if (mIdx>=0) messages.splice(mIdx, 1); // remove item
mIdx=-1; mIdx=-1;
} else { // add/modify } else { // add/modify
if (event.t=="add") event.new=true; // new message if (event.t=="add"){
if(event.new === undefined ) { // If 'new' has not been set yet, set it
event.new=true; // Assume it should be new
}
}
if (mIdx<0) { if (mIdx<0) {
mIdx=0; mIdx=0;
messages.unshift(event); // add new messages to the beginning messages.unshift(event); // add new messages to the beginning
@ -27,7 +31,11 @@ exports.pushMessage = function(event) {
// if in app, process immediately // if in app, process immediately
if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
// ok, saved now - we only care if it's new // ok, saved now - we only care if it's new
if (event.t!="add") return; if (event.t!="add") {
return;
} else if(event.new == false) {
return;
}
// otherwise load messages/show widget // otherwise load messages/show widget
var loadMessages = Bangle.CLOCK || event.important; var loadMessages = Bangle.CLOCK || event.important;
// first, buzz // first, buzz

View File

@ -1,2 +1,3 @@
0.01: New App! 0.01: New App!
0.02: Add posibillity to generate Wifi code. 0.02: Add posibillity to generate Wifi code.
0.03: Forces integer scaling and adds more configuration (error correction, description, display)

View File

@ -3,10 +3,11 @@
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
</head> </head>
<body> <body>
<input type="radio" id="useURL" name="mode" checked/> <input type="radio" id="useTEXT" name="mode" checked/>
<label for="useURL">Use URL:</label> <label for="useTEXT">Use text (for example an URL):</label>
<input type="text" id="url" class="form-input" value="http://espruino.com"> <input type="text" id="text" class="form-input" value="www.espruino.com">
<hr> <hr>
<input type="radio" id="useWIFI" name="mode"/> <input type="radio" id="useWIFI" name="mode"/>
<label for="useWIFI">Use Wifi Credentials:</label> <label for="useWIFI">Use Wifi Credentials:</label>
<input type="text" id="ssid" class="form-input" value=""> <input type="text" id="ssid" class="form-input" value="">
@ -25,30 +26,54 @@
<input type="checkbox" id="hidden" name="hidden"/> <input type="checkbox" id="hidden" name="hidden"/>
<label for="hidden">Wifi is hidden</label> <label for="hidden">Wifi is hidden</label>
</div> </div>
<hr>
<p id="errors" style="color:Tomato;"></p>
<p>Try your QR Code: <div id="qrcode"></div></p> <p>Try your QR Code: <div id="qrcode"></div></p>
<hr>
<p>Additional options:</p>
<input type="checkbox" id="preventIntegerScaling" name="preventIntegerScaling"/>
<label for="preventIntegerScaling">Prevent integer scaling</label></br>
<input type="checkbox" id="boostBacklight" name="boostBacklight"/>
<label for="boostBacklight">Set backlight to max. while QR is shown</label></br>
<input type="checkbox" id="stayOn" name="stayOn"/>
<label for="stayOn">Do not lock or dim while showing QR</label></br>
<input type="checkbox" id="hideDescription" name="hideDescription"/>
<label for="hideDescription">Hide Description</label></br>
<label for="description">Replace default description:</label>
<input type="text" id="description" class="form-input" value="">
<label for="correction">Error correction level:</label>
<div class="input-group">
<select name="correction" id="correction" class="form-control">
<option value="1">L - Low - 7%</option>
<option value="0">M - Medium - 15%</option>
<option value="3">Q - Quartile - 25%</option>
<option value="2">H - High - 30%</option>
</select>
</div>
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p> <p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
<script src="../../core/lib/qrcode.min.js"></script><!-- https://davidshimjs.github.io/qrcodejs/ --> <script src="../../core/lib/qrcode.min.js"></script><!-- https://davidshimjs.github.io/qrcodejs/ -->
<script src="../../core/lib/heatshrink.js"></script> <script src="../../core/lib/heatshrink.js"></script>
<script src="../../core/lib/imageconverter.js"></script> <script src="../../core/lib/imageconverter.js"></script>
<script> <script>
var targetWidth = 200; var targetSize = 200;
var targetHeight = 200;
function onInit(device) { function onInit(device) {
if (device && device.info && device.info.g) { if (device && device.info && device.info.g) {
targetWidth = device.info.g.width - 20; border = 4;
targetHeight = device.info.g.height - 20; targetSize = Math.min(device.info.g.width - border, device.info.g.height - border);
} }
qrcode = new QRCode("qrcode", { qrcode = new QRCode("qrcode", {
text: document.getElementById("url").value, text: document.getElementById("text").value,
width: targetWidth,
height: targetHeight,
colorDark : "#000000", colorDark : "#000000",
colorLight : "#ffffff", colorLight : "#ffffff",
}); });
refreshQRCode();
} }
//https://github.com/evgeni/qifi/blob/gh-pages/index.html#L168 //https://github.com/evgeni/qifi/blob/gh-pages/index.html#L168
@ -75,40 +100,82 @@
return qrstring; return qrstring;
} }
function refreshQRCode(){ function refreshQRCode(){
document.getElementById("errors").innerText="";
qrcode.clear(); // clear the code. qrcode.clear(); // clear the code.
var qrText = "";
if(document.getElementById("useWIFI").checked){ if(document.getElementById("useWIFI").checked){
const ssid = document.getElementById("ssid").value; const ssid = document.getElementById("ssid").value;
const password = document.getElementById("password").value; const password = document.getElementById("password").value;
const encryption = document.getElementById("encryption").value; const encryption = document.getElementById("encryption").value;
const hidden = document.getElementById("hidden").checked; const hidden = document.getElementById("hidden").checked;
const wifiString = generateWifiString(ssid, password, hidden, encryption); const wifiString = generateWifiString(ssid, password, hidden, encryption);
qrcode.makeCode(wifiString); qrText= wifiString;
}else{ } else {
qrcode.makeCode(document.getElementById("url").value); qrText = document.getElementById("text").value;
}
qrcode._htOption.text = qrText;
qrcode._htOption.correctLevel = parseInt(document.getElementById("correction").value);
try {
qrcode.makeCode(qrText);
} catch (error) {
document.getElementById("errors").innerText="Error: QR could not be created.";
console.error(error);
}
var finalSizeQr=targetSize;
var finalSizeCanvas=targetSize;
var integerScale = Math.max(Math.floor(targetSize / (qrcode._oQRCode.moduleCount + 1)),1);
if (integerScale == 1) document.getElementById("errors").innerText = "Warning, QR will probably be too small to properly scan. Try less data or less error correction.";
if (!document.getElementById("preventIntegerScaling").checked){
finalSizeQr = integerScale * (qrcode._oQRCode.moduleCount + 1);
finalSizeCanvas = finalSizeQr - 1;
}
qrcode._htOption.width = finalSizeQr;
qrcode._htOption.height = finalSizeQr;
document.getElementsByTagName("canvas")[0].width = finalSizeCanvas;
document.getElementsByTagName("canvas")[0].height = finalSizeCanvas;
try {
qrcode.makeCode(qrText);
} catch (error) {
document.getElementById("errors").innerText="Error: QR could not be created.";
console.error(error);
} }
} }
var qrcode; var qrcode;
document.getElementById("url").addEventListener("change", refreshQRCode);
document.getElementById("ssid").addEventListener("change",refreshQRCode); document.getElementById("ssid").addEventListener("change",refreshQRCode);
document.getElementById("text").addEventListener("change",refreshQRCode);
document.getElementById("password").addEventListener("change",refreshQRCode); document.getElementById("password").addEventListener("change",refreshQRCode);
document.getElementById("encryption").addEventListener("change",refreshQRCode); document.getElementById("encryption").addEventListener("change",refreshQRCode);
document.getElementById("hidden").addEventListener("change",refreshQRCode); document.getElementById("hidden").addEventListener("change",refreshQRCode);
document.getElementById("useURL").addEventListener("change",refreshQRCode); document.getElementById("useTEXT").addEventListener("change",refreshQRCode);
document.getElementById("useWIFI").addEventListener("change",refreshQRCode); document.getElementById("useWIFI").addEventListener("change",refreshQRCode);
document.getElementById("preventIntegerScaling").addEventListener("change",refreshQRCode);
document.getElementById("correction").addEventListener("change",refreshQRCode);
document.getElementById("upload").addEventListener("click", function() { document.getElementById("upload").addEventListener("click", function() {
var content = document.getElementById("url").value; var content = document.getElementById("text").value;
if(document.getElementById("useWIFI").checked){ if(document.getElementById("useWIFI").checked){
content = document.getElementById("ssid").value content = document.getElementById("ssid").value
} }
if(!(document.getElementById("description").value === "")){
content = document.getElementById("description").value;
}
var img = imageconverter.canvastoString(document.getElementsByTagName("canvas")[0],{mode:"1bit",output:"string",compression:true}); var img = imageconverter.canvastoString(document.getElementsByTagName("canvas")[0],{mode:"1bit",output:"string",compression:true});
var app = `var img = ${img}; var app = `var img = ${img};
var content = ${JSON.stringify(content)}; ${document.getElementById("boostBacklight").checked ? 'Bangle.setLCDBrightness(1);' : ''}
${document.getElementById("stayOn").checked ? 'Bangle.setLCDTimeout(0);' : ''}
${document.getElementById("hideDescription").checked ? '' : `var content = ${JSON.stringify(content)};`}
g.clear(1).setColor(1,1,1).setBgColor(0,0,0); g.clear(1).setColor(1,1,1).setBgColor(0,0,0);
g.fillRect(0,0,g.getWidth()-1,g.getHeight()-1); g.fillRect(0,0,g.getWidth()-1,g.getHeight()-1);
g.drawImage(img,(g.getWidth()-img[0])/2,(g.getHeight()-img[1])/2); g.drawImage(img,(g.getWidth()-img[0])/2,(g.getHeight()-img[1])/2);
g.setFontAlign(0,0).setFont("6x8").setColor(0,0,0); ${ document.getElementById("hideDescription").checked ? '' : `g.setFontAlign(0,0).setFont("6x8").setColor(0,0,0);
g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4)); g.drawString(content,g.getWidth()/2,g.getHeight()-(g.getHeight()-img[1])/4));
`}
g.setColor(1,1,1); g.setColor(1,1,1);
`; `;
sendCustomizedApp({ sendCustomizedApp({

1
apps/scribble/ChangeLog Normal file
View File

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

15
apps/scribble/README.md Normal file
View File

@ -0,0 +1,15 @@
# Scribble
A tree-based keyboard, inspired by Tertiary Text on Pebble.
![](screenshot.png)
## Usage
Tap a button to select text.
Swipe left to right for enter space.
Swipe right to left to delete.
## Creator
enricorov

View File

@ -0,0 +1,14 @@
{ "id": "scribble",
"name": "Scribble",
"shortName":"Scribble",
"version":"0.01",
"description": "A keyboard on your wrist!",
"icon": "app.png",
"tags": "keyboard, text, scribble",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"scribble.app.js","url":"app.js"},
{"name":"scribble.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwMB/4A2/IFE+IFE+YFE84FE44FE54SEz/jAocfDAk/54EC/1/x4FC/l/z4FDCQJGD/wFD+IYBIwYSBIwf4IwhfEIwuPIwkPIwMAj//g/P/gFCkOP/AEB/8wj5+Dn0/Aoc8n/4JAU4v/8gYFBaYWAJ4MHAoPwEgMPOgUfLogJCBYQFE+AFD8BHB/EAAAV/AoYyCB4IKBc6QA=="))

469
apps/scribble/app.js Normal file
View File

@ -0,0 +1,469 @@
const black = "#000000";
const white = "#ffffff";
const gray1 = "#444444";
const gray2 = "#888888";
const gray3 = "#bbbbbb";
const red = "#FF0000";
const green = "#00FF00";
const blue = "#0000FF";
const transp = -1;
const abc = "abcdefghijklmnopqrstuvwxyz1234567890";
// const abc_up = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
const uppercase = 1;
var last_layer = false; // set to true at the last layer of the tree
let chunk_size = 6;
const font_height = 2;
const global_font = "Dennis8";
require("FontDennis8").add(Graphics);
const editable_buf = "Scribble";
const left = 3;
const _screen_mid = g.getWidth() / 2;
const right = 176 - 4;
const box_size = {
w: _screen_mid - 6,
h: 46,
};
const spacing = 4;
const border = 4;
const top_start = 25;
const pos_y = [
top_start,
top_start + (box_size.h + spacing),
top_start + (box_size.h + spacing) * 2,
];
// list of points to render
const points = {
"3x2": [{ x: left, y: pos_y[0] },
{ x: left, y: pos_y[1] },
{ x: left, y: pos_y[2] },
{ x: _screen_mid + 2, y: pos_y[0] },
{ x: _screen_mid + 2, y: pos_y[1] },
{ x: _screen_mid + 2, y: pos_y[2] },
]
};
g.theme = {
fg: white,
bg: black,
fg2: white,
bg2: black,
fgH: black,
bgH: red,
dark: false,
};
const maxX = g.getWidth();
const maxY = g.getHeight();
const fontSize = g.getWidth() > 200 ? 2 : 1;
const rowN = 7;
const colN = 7;
const headerH = maxY / 7;
const rowH = (maxY - headerH) / rowN;
const colW = maxX / colN;
function getRndInteger(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
class Window {
constructor(label, bgCol) {
this.label = "win_"
this.label += (typeof label !== "undefined") ? label : "Unset";
console.log(`Constructing Window ${this.label}, args: ${arguments}`)
this.bgCol = bgCol;
this.layers = [];
}
push(layer) {
layer.label=`${this.layers.length}_${layer.label}`;
this.layers.push(layer);
}
pop() {
this.layers.pop();
}
top_layer() {
return this.layers[this.layers.length - 1];
}
render() {
if (this.bgCol !== transp) {
console.log(`${this.label}: filling bg in ${this.bgCol}`);
g.setColor(this.bgCol);
g.fillRect(0, 0, g.getWidth(), g.getHeight());
}
let i = 0;
this.layers.forEach((lyr) => {
// console.log(`Rendering Layer ${i} ${lyr.label}`)
i++;
lyr.render();
});
}
}
class Layer {
constructor(label) {
this.label = "lyr_"
this.label += (typeof label !== "undefined") ? label : "Unset";
console.log(`Constructing Layer ${this.label}, args: ${arguments}`)
this.items = [];
// console.log(`bg is ${bg} type ${typeof bg}`)
}
push(button) {
this.items.push(button);
}
setLabel(label) {
this.label = label;
}
parseTaps(xy) {
this.items.forEach(item => {
// // print(item)
if (item.was_tapped(xy)) {
// pass parent layer to the tapped button
item.callback(this);
}
});
}
render() {
this.items.forEach((item) => {
item.render();
});
}
}
class BTN_layer extends Layer {
constructor(label, layout) {
super();
Layer.call(this, label)
this.alphabet = (uppercase) ? abc.toUpperCase() : abc;
console.log(`Constructing BTN_Layer ${this.label}, layout ${this.layout}`)
if (layout in points) {
this.create_layout(layout);
}
else {
throw `Invalid layout passed ->[${layout}]`;
}
// // print(this);
}
render() {
Layer.prototype.render.call(this);
}
create_layout(layout) {
console.log(`Creating layout ${layout}`);
let start_p = 0;
this.items = this.push_buttons(points[layout], this.alphabet, start_p, chunk_size)
}
push_buttons(points, in_string, start_p) {
items = [];
spacer = "" // char interposed b/w the two halves of text per button
for (let i = 0; i < points.length; i++) {
substr = `${in_string.substring(
start_p,
start_p + chunk_size / 2
)}${spacer}${in_string.substring(start_p + chunk_size / 2, start_p + chunk_size)}`;
btn_label =
uppercase === 1
? substr.toUpperCase()
: substr;
start_p += chunk_size;
items.push(
new Button(
i, // ID of button
points[i].x, // left
points[i].y, // top
btn_label, // text to render in the button
box_size.w, // width
box_size.h, // height
g.theme.bg, // box bg
white, // box fill
black // text col
)
);
}
return items;
}
update_labels(in_string, start_p, chk_size) {
// print(`Updating labels | in_string ${in_string} start_p ${start_p} chk_size ${chk_size}`);
in_string.replace('\n', ''); // remove newlines just in case
spacer = "" // char interposed b/w the two halves of text per button
for (let i = 0; i < this.items.length; i++) {
item = this.items[i];
substr = (chk_size < 3)
? in_string.substring(start_p + chk_size * i, start_p + (chk_size * (i + 1)))
: `${in_string.substring(
start_p + chk_size * i,
start_p + chk_size * i + chk_size / 2
)}${spacer}${in_string.substring(start_p + chk_size * i + chk_size / 2, start_p + chk_size * i + chk_size)}`;
// // print(`(chk_size > 3): ${(chk_size > 3)}`)
// print(`Label ${i} -> ${substr}`);
item.setLabel(substr);
}
}
zoom_in(id) {
let start_p = id * chunk_size;
// print(`Zooming in | start_p ${start_p}`)
if (chunk_size % this.items.length !== 0) {
throw `Chunk size [${chunk_size}] does not fit #btns [${this.items.length}]`
}
subchunk_size = chunk_size / this.items.length;
substr = this.alphabet.substring(start_p, start_p + chunk_size);
// print(`substr ${substr}`);
// print(`subchunk_size ${subchunk_size}`);
this.update_labels(substr, 0, subchunk_size);
}
}
class Button {
constructor(id, x, y, text, w, h, col, bgCol, txtCol, font) {
this.id = id;
this.label = `btn_${this.id}`;
this.text = text;
this.x1 = x;
this.y1 = y;
this.w = w;
this.h = h;
this.col = typeof col !== "undefined" ? col : black;
this.bgCol = typeof bgCol !== "undefined" ? bgCol : gray2;
this.txtCol = typeof txtCol !== "undefined" ? txtCol : black;
// this.font = font;
this.x2 = this.x1 + this.w;
this.y2 = this.y1 + this.h;
this.center = {
x: (this.x1 + this.x2) / 2,
y: (this.y1 + this.y2) / 2,
};
console.log(`Constructed button `)
// // print(this);
}
render() {
// console.log(
// `Button ${this.text} -> P1: (${this.x1}, ${this.y1}) | P2: (${this.x2}, ${this.y2})`
// );
g.setColor(this.bgCol);
g.fillRect(this.x1, this.y1, this.x2, this.y2);
g.setColor(this.col);
g.drawRect(this.x1, this.y1, this.x2, this.y2);
g.setColor(this.txtCol);
g.setFontAlign(0, 0).setFont(global_font, font_height);
g.drawString(this.text, this.center.x, this.center.y);
}
// short tap callback func
callback(parent_layer) {
// print(`Tapped button ${this.id}`);
// this.highlight(); // TODO set up highlighting
if (last_layer) {
l_text.items[0].text += this.text;
// print(`Updated buffer to ${l_text.items[0].text}`)
parent_layer.update_labels(parent_layer.alphabet, 0, chunk_size);
last_layer = false;
}
else {
parent_layer.zoom_in(this.id);
last_layer = true;
}
}
was_tapped(xy) {
var x = xy.x;
var y = xy.y;
if ((x > this.x1 && x < this.x2) && (y > this.y1 && y < this.y2)) {
return true;
}
else {
return false;
}
}
setLabel(lbl) {
// // print(`Button ${this.id}, updating label ${this.text} with ${lbl}`);
this.text = lbl;
}
getLabel(lbl) {
return this.label;
}
highlight() {
g.setColor(g.theme.bgH);
g.fillRect(this.x1, this.y1, this.x2, this.y2);
g.setColor(g.theme.fgH);
g.drawRect(this.x1, this.y1, this.x2, this.y2);
g.setColor(this.fg);
g.setFontAlign(0, 0).setFont(global_font, font_height);
g.drawString(this.text, this.center.x, this.center.y);
}
}
class TextBox {
constructor(x, y, text, col) {
// x and y are the center points
this.x = x;
this.y = y;
this.text = (typeof text !== undefined) ? text : "Default";
this.col = (typeof col !== undefined) ? col : red;
// console.log(`Constr TextBox ${this.text} -> Center: (${this.x}, ${this.y}) | Col ${this.col}`);
}
render() {
// console.log(`Rendering TextBox`)
var align_center = (0, 1);
var align_right = (0, 0);
alignment = (g.stringWidth(this.text) < g.getWidth()) ? align_center : align_right;
// coords = (g.stringWidth(this.text) < g.getWidth()- 20) ? {x:this.x, y:this.y} : {x:g.getWidth()-border, y:this.y}
coords = { x: this.x, y: this.y };
g.setColor(this.col);
g.setFontAlign(0, 0).setFont(global_font, font_height);
g.drawString(this.text, coords.x, coords.y);
}
}
/* Screen refresh *************************************/
function draw(obj) {
console.log("draw()");
obj.render();
}
let tickTimer;
function clearTickTimer() {
if (tickTimer) {
clearTimeout(tickTimer);
tickTimer = undefined;
}
}
function queueNextTick() {
clearTickTimer();
tickTimer = setTimeout(tick, 5000);
}
function tick() {
console.log("tick");
draw(window);
// queueNextTick();
}
/* Init **********************************************/
var window = new Window("abc", red);
var l_btns = new BTN_layer("btns", "3x2");
var l_text = new Layer("text"); // black
var box = new TextBox(
_screen_mid,
12,
editable_buf,
white
);
l_text.push(box);
window.push(l_text);
window.push(l_btns);
// Set up callbacks for touches
Bangle.on('touch', function (button, xy) {
window.top_layer().parseTaps(xy);
window.render();
});
Bangle.on('swipe', function (direction) {
console.log(`Swipe dir ${direction}`);
if (direction === -1) { // left
l_text.items[0].text = l_text.items[0].text.slice(0, -1);
} else if (direction == 1) { // right
l_text.items[0].text += ' ';
}
window.render();
});
// Clear the screen once, at startup
g.clear();
// Start ticking
tick();

BIN
apps/scribble/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB