mirror of https://github.com/espruino/BangleApps
Merge branch 'espruino:master' into master
commit
c7b723f87e
10
apps.json
10
apps.json
|
@ -42,7 +42,7 @@
|
|||
"name": "Launcher (Default)",
|
||||
"shortName":"Launcher",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"tags": "tool,system,launcher,b2",
|
||||
"type":"launch",
|
||||
|
@ -107,7 +107,7 @@
|
|||
"name": "Fullscreen Notifications",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.11",
|
||||
"version":"0.12",
|
||||
"description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.",
|
||||
"tags": "widget,b2",
|
||||
"type": "notify",
|
||||
|
@ -585,7 +585,7 @@
|
|||
{ "id": "weather",
|
||||
"name": "Weather",
|
||||
"icon": "icon.png",
|
||||
"version":"0.08",
|
||||
"version":"0.09",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"readme": "readme.md",
|
||||
"tags": "widget,outdoors",
|
||||
|
@ -1381,7 +1381,7 @@
|
|||
{ "id": "barclock",
|
||||
"name": "Bar Clock",
|
||||
"icon": "clock-bar.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "A simple digital clock showing seconds as a bar",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
|
@ -2058,7 +2058,7 @@
|
|||
"name": "Find Phone",
|
||||
"shortName":"Find Phone",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.",
|
||||
"tags": "tool,android",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Clock does not start if app Languages is not installed
|
||||
0.06: Improve accuracy
|
||||
0.07: Update to use Bangle.setUI instead of setWatch
|
||||
0.08: Use theme colors, Layout library
|
|
@ -3,167 +3,102 @@
|
|||
* A simple digital clock showing seconds as a bar
|
||||
**/
|
||||
// Check settings for what type our clock should be
|
||||
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
|
||||
let locale = require('locale')
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
let locale = require("locale");
|
||||
{ // add some more info to locale
|
||||
let date = new Date()
|
||||
date.setFullYear(1111)
|
||||
date.setMonth(1, 3) // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true)
|
||||
locale.dayFirst = /3.*2/.test(localized)
|
||||
let date = new Date();
|
||||
date.setFullYear(1111);
|
||||
date.setMonth(1, 3); // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true);
|
||||
locale.dayFirst = /3.*2/.test(localized);
|
||||
|
||||
locale.hasMeridian = false
|
||||
if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed
|
||||
locale.hasMeridian = (locale.meridian(date) !== '')
|
||||
locale.hasMeridian = false;
|
||||
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
|
||||
locale.hasMeridian = (locale.meridian(date)!=="");
|
||||
}
|
||||
|
||||
}
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
Bangle.loadWidgets();
|
||||
function renderBar(l) {
|
||||
if (!this.fraction) {
|
||||
// zero-size fillRect stills draws one line of pixels, we don't want that
|
||||
return;
|
||||
}
|
||||
const width = this.fraction*l.w;
|
||||
g.fillRect(l.x, l.y, width-1, l.y+l.height-1);
|
||||
}
|
||||
|
||||
// hardcoded "settings"
|
||||
const settings = {
|
||||
time: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
ampm: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: 2,
|
||||
const Layout = require("Layout");
|
||||
const layout = new Layout({
|
||||
type: "v", c: [
|
||||
{
|
||||
type: "h", c: [
|
||||
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
|
||||
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
|
||||
],
|
||||
},
|
||||
},
|
||||
date: {
|
||||
color: -1,
|
||||
font: 'Vector',
|
||||
size: 20,
|
||||
middle: screen.height - 20, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
bar: {
|
||||
color: -1,
|
||||
top: 155, // just below time
|
||||
thickness: 6, // matches 24h time "pixel" size
|
||||
},
|
||||
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
||||
{height: 40},
|
||||
{id: "date", type: "txt", font: "10%", valign: 1},
|
||||
],
|
||||
}, false, {lazy: true});
|
||||
// adjustments based on screen size and whether we display am/pm
|
||||
let thickness; // bar thickness, same as time font "pixel block" size
|
||||
if (is12Hour) {
|
||||
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
|
||||
thickness = Math.floor((g.getWidth()-24)/(5*6));
|
||||
} else {
|
||||
layout.ampm.label = "";
|
||||
thickness = Math.floor(g.getWidth()/(5*6));
|
||||
}
|
||||
layout.bar.height = thickness+1;
|
||||
layout.time.font = "6x8:"+thickness;
|
||||
layout.update();
|
||||
|
||||
const SECONDS_PER_MINUTE = 60
|
||||
|
||||
const timeText = function (date) {
|
||||
function timeText(date) {
|
||||
if (!is12Hour) {
|
||||
return locale.time(date, true)
|
||||
return locale.time(date, true);
|
||||
}
|
||||
const date12 = new Date(date.getTime())
|
||||
const hours = date12.getHours()
|
||||
if (hours === 0) {
|
||||
date12.setHours(12)
|
||||
} else if (hours > 12) {
|
||||
date12.setHours(hours - 12)
|
||||
const date12 = new Date(date.getTime());
|
||||
const hours = date12.getHours();
|
||||
if (hours===0) {
|
||||
date12.setHours(12);
|
||||
} else if (hours>12) {
|
||||
date12.setHours(hours-12);
|
||||
}
|
||||
return locale.time(date12, true)
|
||||
return locale.time(date12, true);
|
||||
}
|
||||
const ampmText = function (date) {
|
||||
return is12Hour ? locale.meridian(date) : ''
|
||||
function ampmText(date) {
|
||||
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
|
||||
}
|
||||
|
||||
const dateText = function (date) {
|
||||
function dateText(date) {
|
||||
const dayName = locale.dow(date, true),
|
||||
month = locale.month(date, true),
|
||||
day = date.getDate()
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
|
||||
return `${dayName} ${dayMonth}`
|
||||
day = date.getDate();
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
|
||||
return `${dayName} ${dayMonth}`;
|
||||
}
|
||||
|
||||
const drawDateTime = function (date) {
|
||||
const t = settings.time
|
||||
g.setColor(t.color)
|
||||
g.setFont(t.font, t.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(timeText(date), t.center, t.middle, true)
|
||||
if (is12Hour && locale.hasMeridian) {
|
||||
const a = settings.time.ampm
|
||||
g.setColor(a.color)
|
||||
g.setFont(a.font, a.size)
|
||||
g.setFontAlign(1, -1) // right top
|
||||
// at right edge of screen, aligned with time bottom
|
||||
const left = screen.width - a.size * 2,
|
||||
top = t.middle + t.size - a.size
|
||||
g.drawString(ampmText(date), left, top, true)
|
||||
}
|
||||
draw = function draw() {
|
||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
||||
const date = new Date();
|
||||
layout.time.label = timeText(date);
|
||||
layout.ampm.label = ampmText(date);
|
||||
layout.date.label = dateText(date);
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
||||
layout.render();
|
||||
// schedule update at start of next second
|
||||
const millis = date.getMilliseconds();
|
||||
setTimeout(draw, 1000-millis);
|
||||
};
|
||||
|
||||
const d = settings.date
|
||||
g.setColor(d.color)
|
||||
g.setFont(d.font, d.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(dateText(date), d.center, d.middle, true)
|
||||
}
|
||||
|
||||
const drawBar = function (date) {
|
||||
const b = settings.bar
|
||||
const seconds = date.getSeconds()
|
||||
if (seconds === 0) {
|
||||
// zero-size rect stills draws one line of pixels, we don't want that
|
||||
return
|
||||
}
|
||||
const fraction = seconds / SECONDS_PER_MINUTE,
|
||||
width = fraction * screen.width
|
||||
g.setColor(b.color)
|
||||
g.fillRect(0, b.top, width, b.top + b.thickness)
|
||||
}
|
||||
|
||||
const clearScreen = function () {
|
||||
g.setColor(0)
|
||||
const timeTop = settings.time.middle - (settings.time.size * 4)
|
||||
g.fillRect(0, timeTop, screen.width, screen.height)
|
||||
}
|
||||
|
||||
let lastSeconds, tTick
|
||||
const tick = function () {
|
||||
g.reset()
|
||||
const date = new Date()
|
||||
const seconds = date.getSeconds()
|
||||
if (lastSeconds > seconds) {
|
||||
// new minute
|
||||
clearScreen()
|
||||
drawDateTime(date)
|
||||
}
|
||||
// the bar only gets larger, so drawing on top of the previous one is fine
|
||||
drawBar(date)
|
||||
lastSeconds = seconds
|
||||
// schedule next update
|
||||
const millis = date.getMilliseconds()
|
||||
tTick = setTimeout(tick, 1000-millis)
|
||||
}
|
||||
|
||||
const start = function () {
|
||||
lastSeconds = 99 // force redraw
|
||||
tick()
|
||||
}
|
||||
const stop = function () {
|
||||
if (tTick) {
|
||||
clearTimeout(tTick)
|
||||
tTick = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
g.clear()
|
||||
Bangle.loadWidgets()
|
||||
Bangle.drawWidgets()
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
Bangle.on("lcdPower", function(on) {
|
||||
if (on) {
|
||||
start()
|
||||
} else {
|
||||
stop()
|
||||
draw();
|
||||
}
|
||||
})
|
||||
start()
|
||||
});
|
||||
g.reset().clear();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.3 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: First Version
|
||||
0.02: Remove HID requirement, update screen
|
||||
0.03: Fix for Bangle 2, toggle find with top half of screen, exit touch bottom half of screen
|
||||
|
|
|
@ -6,3 +6,8 @@ Ring your phone via GadgetBridge if you lost it somewhere.
|
|||
2. Lose phone
|
||||
3. Open app
|
||||
4. Click any button or screen
|
||||
|
||||
## On a Bangle 2
|
||||
|
||||
- You can touch the top half of the screen to toggle Find / Stop
|
||||
- You can touch the bottom half of the screen to exit the app.
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
//notify your phone
|
||||
|
||||
const fontSize = g.getWidth() / 8;
|
||||
var finding = false;
|
||||
|
||||
function draw() {
|
||||
// show message
|
||||
g.clear(1);
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12",3);
|
||||
g.clear(g.theme.bg);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector", fontSize);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
if (finding) {
|
||||
g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20);
|
||||
g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20);
|
||||
|
@ -17,17 +19,37 @@ function draw() {
|
|||
g.flip();
|
||||
}
|
||||
|
||||
function findPhone(v) {
|
||||
Bluetooth.println(JSON.stringify({t:"findPhone", n:v}));
|
||||
}
|
||||
|
||||
function find(){
|
||||
finding = !finding;
|
||||
draw();
|
||||
Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding}));
|
||||
findPhone(finding);
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
//register all buttons and screen to find phone
|
||||
setWatch(find, BTN1, {repeat:true});
|
||||
setWatch(find, BTN2, {repeat:true});
|
||||
setWatch(find, BTN3, {repeat:true});
|
||||
setWatch(find, BTN4, {repeat:true});
|
||||
setWatch(find, BTN5, {repeat:true});
|
||||
|
||||
if (process.env.HWVERSION == 1) {
|
||||
setWatch(find, BTN2, {repeat:true});
|
||||
setWatch(find, BTN3, {repeat:true});
|
||||
setWatch(find, BTN4, {repeat:true});
|
||||
setWatch(find, BTN5, {repeat:true});
|
||||
}
|
||||
|
||||
if (process.env.HWVERSION == 2) {
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
|
||||
// click top part of the screen to stop start
|
||||
if (xy.y < g.getHeight() / 2) {
|
||||
find();
|
||||
} else {
|
||||
findPhone(false);
|
||||
setTimeout(load, 100); // exit in 100ms
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,3 +52,62 @@ Activity reporting
|
|||
You'll need a Gadgetbridge release *after* version 0.50.0 for Actvity Reporting to be enabled.
|
||||
|
||||
By default heart rate isn't reported, but it can be enabled from `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Record HRM`
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. Switch to using one of the stock watch faces like s7clk or wave on a Bangle 2.
|
||||
|
||||
2. Check that the battery charge level is being seen by the Gadgetbridge App on the phone. This proves that data is getting to your phone.
|
||||
|
||||
### You can test the notifications on the Bangle
|
||||
|
||||
First disconnect from Gadgetbridge on your phone. Then connect
|
||||
through the IDE and enter the following code. You should get a pop
|
||||
up screen on your Bangle. This proves that the watch is correctly
|
||||
setup for notifications.
|
||||
|
||||
|
||||
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
|
||||
|
||||
|
||||
NOTE: On a Bangle 2, this will fail if you have not installed 'Notifications Fullscreen'.
|
||||
|
||||
### Check that notifications are getting through to your Bangle
|
||||
|
||||
* Disconnect your Bangle from Gadgetbridge on your phone.
|
||||
* Connect through the IDE
|
||||
* Run the following bit of code
|
||||
|
||||
var log = [];
|
||||
function GB(d) {
|
||||
log.push(JSON.stringify(d));
|
||||
}
|
||||
|
||||
* Disconnect from the IDE
|
||||
* Connect your Bangle to Gadgetbridge
|
||||
* Call your phone to get a missed call
|
||||
* Disonnect your Bangle to Gadgetbridge
|
||||
* Connect through the IDE
|
||||
* Run the following bit of code
|
||||
|
||||
log;
|
||||
|
||||
If notifications are getting through then you should see something like.
|
||||
|
||||
|
||||
>log
|
||||
=[
|
||||
"{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}",
|
||||
"{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}"
|
||||
]
|
||||
|
||||
|
||||
IMPORTANT: Now reset your Bangle using a BTN3 long press so that the GB() function is restored.
|
||||
|
||||
## References
|
||||
|
||||
[Bangle Gadgetbridge Page](https://www.espruino.com/Gadgetbridge)
|
||||
|
||||
[Gadgetbridge Project Home](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Home)
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Now displays widgets
|
||||
0.05: Use g.theme for colours
|
||||
0.06: Use Bangle.setUI for buttons
|
||||
0.07: Theme colours fix
|
|
@ -33,8 +33,10 @@ function drawMenu() {
|
|||
if (i+menuScroll==selected) {
|
||||
g.setColor(g.theme.bgH).fillRect(0,y,w-1,y+63);
|
||||
g.setColor(g.theme.fgH).drawRect(0,y,w-1,y+63);
|
||||
} else
|
||||
g.clearRect(0,y,w-1,y+63);
|
||||
} else {
|
||||
g.clearRect(0, y, w-1, y+63);
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
g.drawString(app.name,64,y+32);
|
||||
var icon=undefined;
|
||||
if (app.icon) icon = s.read(app.icon);
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
0.09: Add onHide callback
|
||||
0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode
|
||||
0.11: Improvements to help notifications work with themes, Bangle.js 2 support
|
||||
0.12: More use of themes, title now uses theme highlight colors, font adjusts
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
let oldg;
|
||||
let id = null;
|
||||
let hideCallback = null;
|
||||
|
||||
const titleFont = g.getWidth() / 8;
|
||||
/**
|
||||
* See notify/notify.js
|
||||
*/
|
||||
|
@ -63,8 +63,8 @@ exports.show = function(options) {
|
|||
// top bar
|
||||
if (options.title||options.src) {
|
||||
const title = options.title || options.src;
|
||||
g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3);
|
||||
g.setColor(options.titleBgColor||g.theme.bgH).fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(g.theme.fgH).setFontAlign(-1, -1, 0).setFont("Vector", titleFont);
|
||||
g.drawString(title.trim().substring(0, 13), x+5, y+3);
|
||||
if (options.title && options.src) {
|
||||
g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2);
|
||||
|
|
|
@ -7,7 +7,7 @@ const spanishNumberStr = [ ["ZERO"], // 0
|
|||
["CUATRO",''], //4
|
||||
["CINCO",''], //5
|
||||
["SEIS",''], //6
|
||||
["SEITO",''], //7
|
||||
["SIETE",''], //7
|
||||
["OCHO",''], //8
|
||||
["NUEVE",''], // 9,
|
||||
["DIEZ",''], // 10
|
||||
|
@ -20,7 +20,7 @@ const spanishNumberStr = [ ["ZERO"], // 0
|
|||
["DIECI",'SIETE'], // 17
|
||||
["DIECI",'OCHO'], // 18
|
||||
["DIECI",'NEUVE'], // 19
|
||||
["VEINTA",''], // 20
|
||||
["VEINTE",''], // 20
|
||||
["VEINTI",'UNO'], // 21
|
||||
["VEINTI",'DOS'], // 22
|
||||
["VEINTI",'TRES'], // 23
|
||||
|
@ -74,4 +74,4 @@ class SpanishDateFormatter extends DateFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = SpanishDateFormatter;
|
||||
module.exports = SpanishDateFormatter;
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
0.05: Add wind direction.
|
||||
0.06: Use setUI for launcher.
|
||||
0.07: Add theme support and unknown icon.
|
||||
0.08: Refactor and reduce widget ram usage.
|
||||
0.08: Refactor and reduce widget ram usage.
|
||||
0.09: Fix crash when weather.json is absent.
|
|
@ -47,7 +47,7 @@ global.GB = (event) => {
|
|||
};
|
||||
|
||||
exports.get = function() {
|
||||
return storage.readJSON('weather.json').weather;
|
||||
return (storage.readJSON('weather.json')||{}).weather;
|
||||
}
|
||||
|
||||
scheduleExpiry(storage.readJSON('weather.json')||{});
|
||||
|
|
Loading…
Reference in New Issue