Merge branch 'espruino:master' into tinyheads

pull/3611/head
Woogal 2024-10-14 22:48:10 +01:00 committed by GitHub
commit fc8254c09e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
172 changed files with 42904 additions and 708 deletions

View File

@ -112,6 +112,7 @@ module.exports = {
"getSerial": "readonly",
"getTime": "readonly",
"global": "readonly",
"globalThis": "readonly",
"HIGH": "readonly",
"I2C1": "readonly",
"Infinity": "readonly",

View File

@ -24,10 +24,10 @@
var dateStr = require("locale").date(date);
// draw time
g.setFontAlign(0,0).setFont("Vector",48);
g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
g.clearRect(0,y-20,g.getWidth(),y+25); // clear the background
g.drawString(timeStr,x,y);
// draw date
y += 35;
y += 30;
g.setFontAlign(0,0).setFont("6x8");
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
g.drawString(dateStr,x,y);
@ -41,6 +41,8 @@
// Show launcher when middle button pressed
Bangle.setUI({mode:"clock", remove:function() {
// free any memory we allocated to allow fast loading
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}});
// Load widgets
Bangle.loadWidgets();

View File

@ -51,3 +51,7 @@
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
0.47: Fix wrap around when snoozed through midnight
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.
0.49: fix uncaught error if no scroller (Bangle 1). Would happen when trying
to select an alarm in the main menu.
0.50: Bangle.js 2: Long touch of alarm in main menu toggle it on/off. Touching the icon on
the right will do the same.

View File

@ -20,6 +20,8 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
- `Disable All` → Disable _all_ enabled alarms & timers
- `Delete All` → Delete _all_ alarms & timers
On Bangle.js 2 it's possible to toggle alarms, timers and events from the main menu. This is done by clicking the indicator icons of corresponding entries. Or long pressing anywhere on them.
## Creator
- [Gordon Williams](https://github.com/gfwilliams)
@ -29,6 +31,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
- [thyttan](https://github.com/thyttan) - Toggle alarms directly from main menu.
## Attributions

View File

@ -88,13 +88,23 @@ function showMainMenu(scroll, group, scrollback) {
const getGroups = settings.showGroup && !group;
const groups = getGroups ? {} : undefined;
var showAlarm;
const getIcon = (e)=>{return e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff);};
alarms.forEach((e, index) => {
showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
if(showAlarm) {
menu[trimLabel(getLabel(e),40)] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller.scroll, group)
const label = trimLabel(getLabel(e),40);
menu[label] = {
value: e.on,
onchange: (v, touch) => {
if (touch && (2==touch.type || 145<touch.x)) { // Long touch or touched icon.
e.on = v;
saveAndReload();
} else {
setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller?scroller.scroll:undefined, group);
}
},
format: v=>getIcon(e)
};
} else if (getGroups) {
groups[e.group] = undefined;
@ -102,7 +112,7 @@ function showMainMenu(scroll, group, scrollback) {
});
if (!group) {
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller?scroller.scroll:undefined));
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
}

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.48",
"version": "0.50",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",

View File

@ -36,4 +36,6 @@
0.34: Implement API for activity tracks fetching (Recorder app logs).
0.35: Implement API to enable/disable acceleration data tracking.
0.36: Move from wrapper function to {} and let - faster execution at boot
Allow `calendar-` to take an array of items to remove
Allow `calendar-` to take an array of items to remove
0.37: Support Gadgetbridge canned responses
0.38: Don't rewrite settings file on every boot!

View File

@ -10,19 +10,15 @@
let gpsState = {}; // keep information on GPS via Gadgetbridge
// this settings var is deleted after this executes to save memory
let settings = require("Storage").readJSON("android.settings.json",1)||{};
//default alarm settings
if (settings.rp == undefined) settings.rp = true;
if (settings.as == undefined) settings.as = true;
if (settings.vibrate == undefined) settings.vibrate = "..";
require('Storage').writeJSON("android.settings.json", settings);
let settings = Object.assign({rp:true,as:true,vibrate:".."},
require("Storage").readJSON("android.settings.json",1)||{}
);
let _GB = global.GB;
let fetchRecInterval;
global.GB = (event) => {
// feed a copy to other handlers if there were any
if (_GB) setTimeout(_GB,0,Object.assign({},event));
/* TODO: Call handling, fitness */
var HANDLERS = {
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
@ -67,6 +63,9 @@
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
require("messages").pushMessage(event);
},
"canned_responses_sync" : function() {
require("Storage").writeJSON("replies.json", event.d);
},
// {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
"alarm" : function() {
//wipe existing GB alarms

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.36",
"version": "0.38",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -1 +1,4 @@
0.10: New app introduced to the app loader!
0.20: skipped (internal revision)
0.30: skipped (internal revision)
0.40: added functionality for customizing colors

View File

@ -9,5 +9,9 @@ From top to bottom the watch face shows four rows of leds:
* day (yellow leds, top row)
* month (yellow leds, bottom row)
As usual, luminous leds represent a logical one, dark leds a logcal '0'.
The colors are default colors and can be changed at the settings page, also, the outer ring can be disabled.
The rightmost LED represents 1, the second 2, the third 4, the next 8 and so on, i.e. values are the powers of two.
As usual, luminous leds represent a logical one, and "dark" leds a logcal '0'. Dark means the color of the background.
Widgets aren't affected and are shown as normal.

View File

@ -2,135 +2,181 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let drawTimeout;
// Actually draw the watch face
let draw = function()
{
// Bangle.js2 -> 176x176
var x_rgt = g.getWidth();
var y_bot = g.getHeight();
//var x_cntr = x_rgt / 2;
var y_cntr = y_bot / 18*7; // not to high because of widget-field (1/3 is to high)
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
let white = [1,1,1];
let red = [1,0,0];
let green = [0,1,0];
//let blue = [0,0,1];
let yellow = [1,1,0];
//let magenta = [1,0,1];
//let cyan = [0,1,1];
let black = [0,0,0];
let bord_col = white;
let col_off = black;
var col = new Array(red, green, yellow, yellow); // [R,G,B]
let pot_2 = [1, 2, 4, 8, 16, 32]; // array with powers of two, because power-op (**)
// doesn't work -> maybe also faster
var nr_lines = 4; // 4 rows: hour (hr), minute (min), day (day), month (mon)
// Arrays: [hr, min, day, mon]
//No of Bits: 5 6 5 4
let msbits = [4, 5, 4, 3]; // MSB = No bits - 1
let rad = [12, 12, 8, 8]; // radiuses for each row
var x_dist = 28;
let y_dist = [0, 30, 60, 85]; // y-position from y_centr for each row from top
// don't calc. automatic as for x, because of different spaces
var x_offs_rgt = 16; // distance from right border (layout)
const SETTINGSFILE = "BinaryClk.settings.json";
// Date-Time-Array: 4x6 Bit
//var idx_hr = 0;
//var idx_min = 1;
//var idx_day = 2;
//var idx_mon = 3;
var dt_bit_arr = [[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]];
// variables defined from settings
let HourCol;
let MinCol;
let DayCol;
let MonCol;
let RingOn;
var date_time = new Date();
var hr = date_time.getHours(); // 0..23
var min = date_time.getMinutes(); // 0..59
var day = date_time.getDate(); // 1..31
var mon = date_time.getMonth() + 1; // GetMonth() -> 0..11
// color arrays
// !!! don't change order unless change oder in BinaryClk.settings.js !!!
// !!! order must correspond to each other between arrays !!!
let LED_Colors = ["#FFF", "#F00", "#0F0", "#00F", "#FF0", "#F0F", "#0FF", "#000"];
let LED_ColorNames = ["white", "red", "green", "blue", "yellow", "magenta", "cyan", "black"];
let dt_array = [hr, min, day, mon];
////////////////////////////////////////
// compute bit-pattern from time/date and draw leds
////////////////////////////////////////
var line_cnt = 0;
var cnt = 0;
var bit_cnt = 0;
while (line_cnt < nr_lines)
{
////////////////////////////////////////
// compute bit-pattern
bit_cnt = msbits[line_cnt];
while (bit_cnt >= 0)
{
if (dt_array[line_cnt] >= pot_2[bit_cnt])
{
dt_array[line_cnt] -= pot_2[bit_cnt];
dt_bit_arr[line_cnt][bit_cnt] = 1;
}
else
{
dt_bit_arr[line_cnt][bit_cnt] = 0;
}
bit_cnt--;
}
////////////////////////////////////////
// draw leds (first white border for black screen, then led itself)
cnt = 0;
while (cnt <= msbits[line_cnt])
{
g.setColor(bord_col[0], bord_col[1], bord_col[2]);
g.drawCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-20+y_dist[line_cnt], rad[line_cnt]);
if (dt_bit_arr[line_cnt][cnt] == 1)
{
g.setColor(col[line_cnt][0], col[line_cnt][1], col[line_cnt][2]);
}
else
{
g.setColor(col_off[0], col_off[1], col_off[2]);
}
g.fillCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-20+y_dist[line_cnt], rad[line_cnt]-1);
cnt++;
}
line_cnt++;
}
// queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function()
// load settings
let loadSettings = function()
{
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
function def (value, def) {return value !== undefined ? value : def;}
// Show launcher when middle button pressed
Bangle.setUI(
{
mode : "clock",
remove : function()
{
// Called to unload all of the clock app
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
// get name from setting, find index of name and assign corresponding color code by index
HourCol = LED_Colors[LED_ColorNames.indexOf(def(settings.HourCol, "red"))];
MinCol = LED_Colors[LED_ColorNames.indexOf(def(settings.MinCol, "green"))];
DayCol = LED_Colors[LED_ColorNames.indexOf(def(settings.DayCol, "yellow"))];
MonCol = LED_Colors[LED_ColorNames.indexOf(def(settings.MonCol, "yellow"))];
RingOn = def(settings.RingOn, true);
delete settings; // settings in local var -> no more required
}
});
// Load widgets
Bangle.loadWidgets();
draw();
setTimeout(Bangle.drawWidgets,0);
let drawTimeout;
// actually draw the watch face
let draw = function()
{
// Bangle.js2 -> 176x176
var x_rgt = g.getWidth();
var y_bot = g.getHeight();
//var x_cntr = x_rgt / 2;
var y_cntr = y_bot / 18*7;
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
var white = "#FFF";
var black = "#000";
var bord_col = white;
var col_off = black;
var col = new Array(HourCol, MinCol, DayCol, MonCol); // each #rgb
if (g.theme.dark)
{
bord_col = white;
col_off = black;
}
else
{
bord_col = black;
col_off = white;
}
let pwr2 = [1, 2, 4, 8, 16, 32]; // array with powers of 2, because poweroperator '**' doesnt work
// maybe also faster
var no_lines = 4; // 4 rows: hour (hr), minute (min), day (day), month (mon)
var no_hour = 5;
var no_min = 6;
var no_day = 5;
var no_mon = 4;
// arrays: [hr, min, day, mon]
let msbits = [no_hour-1, no_min-1, no_day-1, no_mon-1]; // MSB = No bits - 1
let rad = [13, 13, 9, 9]; // radiuses for each row
var x_dist = 29;
let y_dist = [0, 35, 75, 100]; // y-position from y_centr for each row from top
// don't calc. automatic as for x, because of different spaces
var x_offs_rgt = 15; // offset from right border (layout)
var y_offs_cntr = 25; // vertical offset from center
////////////////////////////////////////
// compute bit-pattern from time/date and draw leds
////////////////////////////////////////
// date-time-array: 4x6 bit
//var idx_hour = 0;
//var idx_min = 1;
//var idx_day = 2;
//var idx_mon = 3;
var dt_bit_arr = [[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]];
var date_time = new Date();
var hour = date_time.getHours(); // 0..23
var min = date_time.getMinutes(); // 0..59
var day = date_time.getDate(); // 1..31
var mon = date_time.getMonth() + 1; // GetMonth() -> 0..11
let dt_array = [hour, min, day, mon];
var line_cnt = 0;
var cnt = 0;
var bit_cnt = 0;
while (line_cnt < no_lines)
{
////////////////////////////////////////
// compute bit-pattern
bit_cnt = msbits[line_cnt];
while (bit_cnt >= 0)
{
if (dt_array[line_cnt] >= pwr2[bit_cnt])
{
dt_array[line_cnt] -= pwr2[bit_cnt];
dt_bit_arr[line_cnt][bit_cnt] = 1;
}
else
{
dt_bit_arr[line_cnt][bit_cnt] = 0;
}
bit_cnt--;
}
////////////////////////////////////////
// draw leds (and border, if enabled)
cnt = 0;
while (cnt <= msbits[line_cnt])
{
if (RingOn) // draw outer ring, if enabled
{
g.setColor(bord_col);
g.drawCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-y_offs_cntr+y_dist[line_cnt], rad[line_cnt]);
}
if (dt_bit_arr[line_cnt][cnt] == 1)
{
g.setColor(col[line_cnt]);
}
else
{
g.setColor(col_off);
}
g.fillCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-y_offs_cntr+y_dist[line_cnt], rad[line_cnt]-1);
cnt++;
}
line_cnt++;
}
// queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function()
{
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
// Init the settings of the app
loadSettings();
// Show launcher when middle button pressed
Bangle.setUI(
{
mode : "clock",
remove : function()
{
// Called to unload all of the clock app
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// Load widgets
Bangle.loadWidgets();
draw();
setTimeout(Bangle.drawWidgets,0);
}

72
apps/blc/blc.settings.js Normal file
View File

@ -0,0 +1,72 @@
// Change settings for BinaryClk
(function(back){
// color array -- don't change order unless change oder in BinaryClk.js
let LED_ColorNames = ["white", "red", "green", "blue", "yellow", "magenta", "cyan", "black"];
var FILE = "BinaryClk.settings.json";
// Load settings
var settings = Object.assign({
HourCol: "red",
MinCol: "green",
DayCol: "yellow",
MonCol: "yellow",
RingOn: true,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings(){
require('Storage').writeJSON(FILE, settings);
}
// Helper method which uses int-based menu item for set of string values
function stringItems(startvalue, writer, values) {
return{
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
format: v => values[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
writer(values[v]);
writeSettings();
}
};
}
// Helper method which breaks string set settings down to local settings object
function stringInSettings(name, values) {
return stringItems(settings[name], v => settings[name] = v, values);
}
// Show the menu
var mainmenu = {
"" : {
"title" : "BinaryCLK"
},
"< Back" : () => back(),
'Color Hour.:': stringInSettings("HourCol", LED_ColorNames),
'Color Minute:': stringInSettings("MinCol", LED_ColorNames),
'Color Day': stringInSettings("DayCol", LED_ColorNames),
'Color Month:': stringInSettings("MonCol", LED_ColorNames),
'LED ring on/off': {
value: (settings.RingOn !== undefined ? settings.RingOn : true),
onchange: v => {
settings.RingOn = v;
writeSettings();
}
},
};
// Show submenues
//var submenu1 = {
//"": {
// "title": "Show sub1..."
//},
//"< Back": () => E.showMenu(mainmenu),
//"ItemName": stringInSettings("settingsVar", ["Yes", "No", "DontCare"]),
//};
E.showMenu(mainmenu);
});

View File

@ -0,0 +1,7 @@
{
"HourCol": "red",
"MinCol": "green",
"DayCol": "yellow",
"MonCol": "yellow",
"RingOn": true
}

View File

@ -1,10 +1,10 @@
{
"id":"blc",
"name":"Binary LED Clock",
"version": "0.10",
"description": "Binary LED Clock with date",
"version": "0.40",
"description": "a binary LED-Clock with time and date and customizable LED-colors",
"icon":"blc-icon.png",
"screenshots": [{"url":"screenshot_blc.bmp"}],
"screenshots": [{"url":"screenshot_blc_1.bmp"},{"url":"screenshot_blc_2.bmp"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
@ -12,6 +12,8 @@
"readme": "README.md",
"storage": [
{"name":"blc.app.js","url":"blc.js"},
{"name":"blc.settings.js","url":"blc.settings.js"},
{"name":"blc.img","url":"blc-icon.js","evaluate":true}
]
],
"data": [{"name":"blc.settings.json"}]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -12,4 +12,5 @@
- [+] Fixed optional seconds not displaying in time
- [+] Fixed drag handler by adding E.stopEventPropagation()
- [+] General code optimization and cleanup
0.09: Revised event handler code
0.09: Revised event handler code
0.10: Revised suffix formatting in getDate function

View File

@ -208,12 +208,23 @@
// 7. String forming helper functions
let getDate = function(short, shortMonth, disableSuffix) {
const date = new Date();
const dayOfMonth = date.getDate();
const day = date.getDate();
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
const year = date.getFullYear();
let suffix = ["st", "nd", "rd"][(dayOfMonth - 1) % 10] || "th";
let dayOfMonthStr = disableSuffix ? dayOfMonth : `${dayOfMonth}${suffix}`;
return `${month} ${dayOfMonthStr}${short ? '' : `, ${year}`}`;
const getSuffix = (day) => {
if (day >= 11 && day <= 13) return 'th';
const lastDigit = day % 10;
switch (lastDigit) {
case 1: return 'st';
case 2: return 'nd';
case 3: return 'rd';
default: return 'th';
}
};
const dayStr = disableSuffix ? day : `${day}${getSuffix(day)}`;
return `${month} ${dayStr}${short ? '' : `, ${year}`}`; // not including year for short version
};
let getDayOfWeek = function(date, short) {

View File

@ -11,14 +11,14 @@
"xOffset": 3,
"yOffset": 0,
"boxPos": {
"x": "0.494",
"x": "0.5",
"y": "0.739"
}
},
"dow": {
"font": "6x8",
"fontSize": 3,
"outline": 1,
"outline": 2,
"color": "bgH",
"outlineColor": "fg",
"border": "#f0f",
@ -27,7 +27,7 @@
"xOffset": 2,
"yOffset": 0,
"boxPos": {
"x": "0.421",
"x": "0.5",
"y": "0.201"
},
"short": false
@ -35,7 +35,7 @@
"date": {
"font": "6x8",
"fontSize": 2,
"outline": 1,
"outline": 2,
"color": "bgH",
"outlineColor": "fg",
"border": "#f0f",
@ -44,7 +44,7 @@
"xOffset": 1,
"yOffset": 0,
"boxPos": {
"x": "0.454",
"x": "0.5",
"y": "0.074"
},
"shortMonth": false,
@ -62,7 +62,7 @@
"xOffset": 2,
"yOffset": 1,
"boxPos": {
"x": "0.494",
"x": "0.5",
"y": "0.926"
},
"prefix": "Steps: "
@ -79,7 +79,7 @@
"xOffset": 2,
"yOffset": 2,
"boxPos": {
"x": "0.805",
"x": "0.8",
"y": "0.427"
},
"suffix": "%"

View File

@ -1,7 +1,7 @@
{
"id": "boxclk",
"name": "Box Clock",
"version": "0.09",
"version": "0.10",
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
"icon": "app.png",
"dependencies" : { "clockbg":"module" },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -5,3 +5,4 @@
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
0.06: Bangle.js 2: Exit with a short press of the physical button
0.07: Bangle.js 2: Exit by pressing upper left corner of the screen
0.08: truncate long numbers (and append '...' to displayed value)

View File

@ -10,9 +10,9 @@
g.clear();
require("Font7x11Numeric7Seg").add(Graphics);
var DEFAULT_SELECTION_NUMBERS = '5', DEFAULT_SELECTION_OPERATORS = '=', DEFAULT_SELECTION_SPECIALS = 'R';
var RIGHT_MARGIN = 20;
var DEFAULT_SELECTION_NUMBERS = '5';
var RESULT_HEIGHT = 40;
var RESULT_MAX_LEN = Math.floor((g.getWidth() - 20) / 14);
var COLORS = {
// [normal, selected]
DEFAULT: ['#7F8183', '#A6A6A7'],
@ -88,28 +88,11 @@ function prepareScreen(screen, grid, defaultColor) {
}
function drawKey(name, k, selected) {
var rMargin = 0;
var bMargin = 0;
var color = k.color || COLORS.DEFAULT;
g.setColor(color[selected ? 1 : 0]);
g.setFont('Vector', 20).setFontAlign(0,0);
g.fillRect(k.xy[0], k.xy[1], k.xy[2], k.xy[3]);
g.setColor(-1);
// correct margins to center the texts
if (name == '0') {
rMargin = (RIGHT_MARGIN * 2) - 7;
} else if (name === '/') {
rMargin = 5;
} else if (name === '*') {
bMargin = 5;
rMargin = 3;
} else if (name === '-') {
rMargin = 3;
} else if (name === 'R' || name === 'N') {
rMargin = k.val === 'C' ? 0 : -9;
} else if (name === '%') {
rMargin = -3;
}
g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2);
}
@ -138,29 +121,21 @@ function drawGlobal() {
screen[k] = specials[k];
}
drawKeys();
var selected = DEFAULT_SELECTION_NUMBERS;
var prevSelected = DEFAULT_SELECTION_NUMBERS;
}
function drawNumbers() {
screen = numbers;
screenColor = COLORS.DEFAULT;
drawKeys();
var selected = DEFAULT_SELECTION_NUMBERS;
var prevSelected = DEFAULT_SELECTION_NUMBERS;
}
function drawOperators() {
screen = operators;
screenColor =COLORS.OPERATOR;
drawKeys();
var selected = DEFAULT_SELECTION_OPERATORS;
var prevSelected = DEFAULT_SELECTION_OPERATORS;
}
function drawSpecials() {
screen = specials;
screenColor = COLORS.SPECIAL;
drawKeys();
var selected = DEFAULT_SELECTION_SPECIALS;
var prevSelected = DEFAULT_SELECTION_SPECIALS;
}
function getIntWithPrecision(x) {
@ -218,8 +193,6 @@ function doMath(x, y, operator) {
}
function displayOutput(num) {
var len;
var minusMarge = 0;
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
g.setColor(-1);
if (num === Infinity || num === -Infinity || isNaN(num)) {
@ -230,9 +203,7 @@ function displayOutput(num) {
num = '-INFINITY';
} else {
num = 'NOT A NUMBER';
minusMarge = -25;
}
len = (num + '').length;
currNumber = null;
results = null;
isDecimal = false;
@ -261,6 +232,9 @@ function displayOutput(num) {
num = num.toString();
num = num.replace("-","- "); // fix padding for '-'
g.setFont('7x11Numeric7Seg', 2);
if (num.length > RESULT_MAX_LEN) {
num = num.substr(0, RESULT_MAX_LEN - 1)+'...';
}
}
g.setFontAlign(1,0);
g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2);

View File

@ -2,7 +2,7 @@
"id": "calculator",
"name": "Calculator",
"shortName": "Calculator",
"version": "0.07",
"version": "0.08",
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
"icon": "calculator.png",
"screenshots": [{"url":"screenshot_calculator.png"}],

View File

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

18
apps/clkinfomsg/README.md Normal file
View File

@ -0,0 +1,18 @@
# Messages Clockinfo
A simple messages counter for clockinfo enabled watchfaces
## Usage
You can choose between read and unread counter.
Tap to go to messages UI.
## Todo / Known Issues
* GB triggers for message read on phone are not handled
* Icons are not consistent
* Maybe use messageicons for handling icon from last notification
## Attributions
All icons used in this app are from [icons8](https://icons8.com).

BIN
apps/clkinfomsg/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,84 @@
(function() {
var unreadImg = function() {
return atob("GBiBAAAAAAAAAAAAAB//+D///D///D///D///D///D///D5mfD5mfD///D///D///D///D///D///B//+APgAAOAAAOAAAAAAAAAAA==");
}
var allImg = function() {
return atob("GBiBAAAAAAAAAAB+AAD/AAPDwA8A8B4AeDgAHDgAHDwAPD8A/D/D/D/n/D///D///D///D///D///D///B//+AAAAAAAAAAAAAAAAA==");
}
var debug = function(o) {
//console.log(o);
}
var msgUnread;
var msgAll;
var msgs = require("messages");
var getAllMSGs = function() {
if (msgAll === undefined) {
debug("msgAll is undefined");
msgAll = msgs.getMessages().filter(m => !['call', 'map', 'music'].includes(m.id)).length;
}
return msgAll;
}
var getUnreadMGS = function() {
if (msgUnread === undefined) {
debug("msgUnread is undefined");
msgUnread = msgs.getMessages().filter(m => m.new && !['call', 'map', 'music'].includes(m.id)).length;
}
return msgUnread;
}
var msgCounter = function(type, msg) {
var msgsNow = msgs.getMessages(msg);
msgUnread = msgsNow.filter(m => m.new && !['call', 'map', 'music'].includes(m.id)).length;
msgAll = msgsNow.filter(m => !['call', 'map', 'music'].includes(m.id)).length;
//TODO find nicer way to redraw current shown CI counter
info.items[0].emit("redraw");
info.items[1].emit("redraw");
}
var info = {
name: "Messages",
img: unreadImg(),
items: [
{ name : "Unread",
get : () => {
return {
text : getUnreadMGS(),
img : unreadImg()
};
},
show : function() {
Bangle.on("message", msgCounter);
},
hide : function() {
Bangle.removeListener("message", msgCounter);
},
run : () => {
require("messages").openGUI();
}
},
{ name : "All",
get : () => {
return {
text : getAllMSGs(),
img : allImg()
};
},
show : function() {
Bangle.on("message", msgCounter);
},
hide : function() {
Bangle.removeListener("message", msgCounter);
},
run : () => {
require("messages").openGUI();
}
}
]
};
return info;
})

View File

@ -0,0 +1,15 @@
{ "id": "clkinfomsg",
"name": "Messages Clockinfo",
"version":"0.01",
"description": "For clocks that display 'clockinfo', this displays the messages count",
"icon": "app.png",
"type": "clkinfo",
"screenshots": [{"url":"screenshot.png"}],
"readme":"README.md",
"tags": "clkinfo",
"supports" : ["BANGLEJS2"],
"dependencies" : { "messages":"app" },
"storage": [
{"name":"clkinfomsg.clkinfo.js","url":"clkinfo.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -50,7 +50,7 @@ extensions).
`load()` returns an array of menu objects, where each object contains a list of menu items:
* `name` : text to display and identify menu object (e.g. weather)
* `img` : a 24x24px image
* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date)
* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date). This is only used by a few clocks, for example `circlesclock`
* `items` : menu items such as temperature, humidity, wind etc.
Note that each item is an object with:

View File

@ -1,3 +1,7 @@
0.01: New App!
0.02: Moved settings into 'Settings->Apps'
0.03: Add 'Squares' option for random squares background
0.03: Add 'Squares' option for random squares background
0.04: More options for different background colors
'Plasma' generative background
Add a 'view' option in settings menu to view the current background
0.05: Random square+plasma speed improvements (~2x faster)

View File

@ -15,6 +15,7 @@ You can either:
* `Random Color` - a new color every time the clock starts
* `Image` - choose from a previously uploaded image
* `Squares` - a randomly generated pattern of squares in the selected color palette
* `Plasma` - a randomly generated 'plasma' pattern of squares in the selected color palette (random noise with a gaussian filter applied)
## Usage in code
@ -30,6 +31,9 @@ background.fillRect(Bangle.appRect);
// to fill just one part of the screen
background.fillRect(x1, y1, x2, y2);
// if you ever need to reload to a new background (this could take ~100ms)
background.reload();
```
You should also add `"dependencies" : { "clockbg":"module" },` to your app's metadata to
@ -39,8 +43,9 @@ ensure that the clock background library is automatically loaded.
A few features could be added that would really improve functionality:
* When 'fast loading', 'random' backgrounds don't update at the moment
* When 'fast loading', 'random' backgrounds don't update at the moment (calling `.reload` can fix this now, but it slows things down)
* Support for >1 image to be uploaded (requires some image management in `interface.html`), and choose randomly between them
* Support for gradients (random colors)
* More types of auto-generated pattern (as long as they can be generated quickly or in the background)
* Storing 'clear' areas of uploaded images so clocks can easily position themselves
* Storing 'clear' areas of uploaded images so clocks can easily position themselves
* Some backgrounds could update themselves in the background (eg a mandelbrot could calculate the one it should display next time while the watch is running)

View File

@ -1,25 +1,49 @@
let settings = Object.assign({
style : "randomcolor",
colors : ["#F00","#0F0","#00F"]
},require("Storage").readJSON("clockbg.json")||{});
if (settings.style=="image")
settings.img = require("Storage").read(settings.fn);
else if (settings.style=="randomcolor") {
settings.style = "color";
let n = (0|(Math.random()*settings.colors.length)) % settings.colors.length;
settings.color = settings.colors[n];
delete settings.colors;
} else if (settings.style=="squares") {
settings.style = "image";
let bpp = (settings.colors.length>4)?4:2;
let bg = Graphics.createArrayBuffer(11,11,bpp,{msb:true});
E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256); // random pixels
bg.palette = new Uint16Array(1<<bpp);
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
settings.img = bg.asImage("string");
settings.imgOpt = {scale:16};
delete settings.colors;
}
let settings;
exports.reload = function() {
settings = Object.assign({
style : "randomcolor",
colors : ["#F00","#0F0","#00F"]
},require("Storage").readJSON("clockbg.json")||{});
if (settings.style=="image")
settings.img = require("Storage").read(settings.fn);
else if (settings.style=="randomcolor") {
settings.style = "color";
let n = (0|(Math.random()*settings.colors.length)) % settings.colors.length;
settings.color = settings.colors[n];
delete settings.colors;
} else if (settings.style=="squares") { // 32ms
settings.style = "image";
let bpp = (settings.colors.length>4)?4:2;
let bg = Graphics.createArrayBuffer(11,11,bpp,{msb:true});
let u32 = new Uint32Array(bg.buffer); // faster to do 1/4 of the ops of E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256);
E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
bg.buffer[bg.buffer.length-1]=Math.random()*256; // 11x11 isn't a multiple of 4 bytes - we need to set the last one!
bg.palette = new Uint16Array(1<<bpp);
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
settings.img = bg;
settings.imgOpt = {scale:16};
delete settings.colors;
} else if (settings.style=="plasma") { // ~47ms
settings.style = "image";
let bg = Graphics.createArrayBuffer(16,16,4,{msb:true});
let u32 = new Uint32Array(bg.buffer); // faster to do 1/4 of the ops of E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256);
E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
bg.filter([ // a gaussian filter to smooth out
1, 4, 7, 4, 1,
4,16,26,16, 4,
7,26,41,26, 7,
4,16,26,16, 4,
1, 4, 7, 4, 1
], { w:5, h:5, div:120, offset:-800 });
bg.palette = new Uint16Array(16);
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
settings.img = bg;
settings.imgOpt = {scale:11};
delete settings.colors;
}
};
exports.reload();
// Fill a rectangle with the current background style, rect = {x,y,w,h}
// eg require("clockbg").fillRect({x:10,y:10,w:50,h:50})

View File

@ -1,10 +1,10 @@
{ "id": "clockbg",
"name": "Clock Backgrounds",
"shortName":"Backgrounds",
"version": "0.03",
"version": "0.05",
"description": "Library that allows clocks to include a custom background (generated on demand or uploaded).",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
"type": "module",
"readme": "README.md",
"provides_modules" : ["clockbg"],

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,122 +1,171 @@
(function(back) {
let settings = Object.assign({
style : "randomcolor",
colors : ["#F00","#0F0","#00F"]
},require("Storage").readJSON("clockbg.json")||{});
let settings = Object.assign({
style : "randomcolor",
colors : ["#F00","#0F0","#00F"]
},require("Storage").readJSON("clockbg.json")||{});
function saveSettings() {
if (settings.style!="image")
delete settings.fn;
if (settings.style!="color")
delete settings.color;
if (settings.style!="randomcolor" && settings.style!="squares")
delete settings.colors;
require("Storage").writeJSON("clockbg.json", settings);
}
function saveSettings() {
if (settings.style!="image")
delete settings.fn;
if (settings.style!="color")
delete settings.color;
if (!["randomcolor","squares","plasma"].includes(settings.style))
delete settings.colors;
require("Storage").writeJSON("clockbg.json", settings);
}
function getColorsImage(cols) {
var bpp = 1;
if (cols.length>4) bpp=4;
else if (cols.length>2) bpp=2;
var w = (cols.length>8)?8:16;
var b = Graphics.createArrayBuffer(w*cols.length,16,bpp);
b.palette = new Uint16Array(1<<bpp);
cols.forEach((c,i)=>{
b.setColor(i).fillRect(i*w,0,i*w+w-1,15);
b.palette[i] = g.toColor(c);
});
return "\0"+b.asImage("string");
}
function getColorsImage(cols) {
var bpp = 1;
if (cols.length>4) bpp=4;
else if (cols.length>2) bpp=2;
var w = (cols.length>8)?8:16;
var b = Graphics.createArrayBuffer(w*cols.length,16,bpp);
b.palette = new Uint16Array(1<<bpp);
cols.forEach((c,i)=>{
b.setColor(i).fillRect(i*w,0,i*w+w-1,15);
b.palette[i] = g.toColor(c);
});
return "\0"+b.asImage("string");
}
function showModeMenu() {
E.showMenu({
"" : {title:/*LANG*/"Background", back:showMainMenu},
/*LANG*/"Solid Color" : function() {
var cols = ["#F00","#0F0","#FF0",
"#00F","#F0F","#0FF",
"#000","#888","#fff",];
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
cols.forEach(col => {
menu["-"+getColorsImage([col])] = () => {
settings.style = "color";
settings.color = col;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
},
/*LANG*/"Random Color" : function() {
var cols = [
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
["#F00","#0F0","#00F"],
// Please add some more!
];
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
cols.forEach(col => {
menu[getColorsImage(col)] = () => {
settings.style = "randomcolor";
settings.colors = col;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
},
/*LANG*/"Image" : function() {
let images = require("Storage").list(/clockbg\..*\.img/);
if (images.length) {
var menu = {"":{title:/*LANG*/"Images", back:showModeMenu}};
images.forEach(im => {
menu[im.slice(8,-4)] = () => {
settings.style = "image";
settings.fn = im;
function showModeMenu() {
E.showMenu({
"" : {title:/*LANG*/"Background", back:showMainMenu},
/*LANG*/"Solid Color" : function() {
var cols = ["#F00","#0F0","#FF0",
"#00F","#F0F","#0FF",
"#000","#888","#fff",];
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
cols.forEach(col => {
menu["-"+getColorsImage([col])] = () => {
settings.style = "color";
settings.color = col;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
},
/*LANG*/"Random Color" : function() {
var cols = [
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
["#F00","#0F0","#00F"],
["#FF0","#F0F","#0FF"],
["#00f","#0bf","#0f7","#3f0","#ff0","#f30","#f07","#b0f"],
["#66f","#6df","#6fb","#8f6","#ff6","#f86","#f6b","#d6f"],
["#007","#057","#073","#170","#770","#710","#703","#507"]
// Please add some more!
];
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
cols.forEach(col => {
menu[getColorsImage(col)] = () => {
settings.style = "randomcolor";
settings.colors = col;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
},
/*LANG*/"Image" : function() {
let images = require("Storage").list(/clockbg\..*\.img/);
if (images.length) {
var menu = {"":{title:/*LANG*/"Images", back:showModeMenu}};
images.forEach(im => {
menu[im.slice(8,-4)] = () => {
settings.style = "image";
settings.fn = im;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
} else {
E.showAlert("Please use App Loader to upload images").then(showModeMenu);
}
},
/*LANG*/"Squares" : function() {
var cols = [ // list of color palettes used as possible square colours - either 4 or 16 entries
["#00f","#05f","#0bf","#0fd","#0f7","#0f1","#3f0","#9f0","#ff0","#f90","#f30","#f01","#f07","#f0d","#b0f","#50f"],
["#44f","#48f","#4df","#4fe","#4fa","#4f6","#7f4","#bf4","#ff4","#fb4","#f74","#f46","#f4a","#f4e","#d4f","#84f"],
["#009","#039","#079","#098","#094","#091","#290","#590","#990","#950","#920","#901","#904","#908","#709","#309"],
["#0FF","#0CC","#088","#044"],
["#FFF","#FBB","#F66","#F44"],
["#FFF","#BBB","#666","#000"],
["#fff","#bbf","#77f","#33f"],
["#fff","#bff","#7fe","#3fd"]
// Please add some more! 4 or 16 only!
];
var menu = {"":{title:/*LANG*/"Squares", back:showModeMenu}};
cols.forEach(col => {
menu[getColorsImage(col)] = () => {
settings.style = "squares";
settings.colors = col;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
},
/*LANG*/"Plasma" : function() {
var cols = [ // list of color palettes used as possible square colours - 16 entries
["#00f","#05f","#0bf","#0fd","#0f7","#0f1","#3f0","#9f0","#ff0","#f90","#f30","#f01","#f07","#f0d","#b0f","#50f"],
["#44f","#48f","#4df","#4fe","#4fa","#4f6","#7f4","#bf4","#ff4","#fb4","#f74","#f46","#f4a","#f4e","#d4f","#84f"],
["#009","#039","#079","#098","#094","#091","#290","#590","#990","#950","#920","#901","#904","#908","#709","#309"],
["#fff","#fef","#fdf","#fcf","#fbf","#fae","#f9e","#f8e","#f7e","#f6e","#f5d","#f4d","#f3d","#f2d","#f1d","#f0c"],
["#fff","#eff","#dff","#cef","#bef","#adf","#9df","#8df","#7cf","#6cf","#5bf","#4bf","#3bf","#2af","#1af","#09f"],
["#000","#010","#020","#130","#140","#250","#260","#270","#380","#390","#4a0","#4b0","#5c0","#5d0","#5e0","#6f0"]
// Please add some more!
];
var menu = {"":{title:/*LANG*/"Plasma", back:showModeMenu}};
cols.forEach(col => {
menu[getColorsImage(col)] = () => {
settings.style = "plasma";
settings.colors = col;
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
} else {
E.showAlert("Please use App Loader to upload images").then(showModeMenu);
}
},
/*LANG*/"Squares" : function() {
/*
a = new Array(16);
a.fill(0);
print(a.map((n,i)=>E.HSBtoRGB(0 + i/16,1,1,24).toString(16).padStart(6,0).replace(/(.).(.).(.)./,"\"#$1$2$3\"")).join(","))
*/
var cols = [ // list of color palettes used as possible square colours - either 4 or 16 entries
["#00f","#05f","#0bf","#0fd","#0f7","#0f1","#3f0","#9f0","#ff0","#f90","#f30","#f01","#f07","#f0d","#b0f","#50f"],
["#0FF","#0CC","#088","#044"],
["#FFF","#FBB","#F66","#F44"],
["#FFF","#BBB","#666","#000"]
// Please add some more!
];
var menu = {"":{title:/*LANG*/"Squares", back:showModeMenu}};
cols.forEach(col => {
menu[getColorsImage(col)] = () => {
settings.style = "squares";
settings.colors = col;
console.log(settings);
saveSettings();
showMainMenu();
};
});
E.showMenu(menu);
}
});
}
});
}
function showMainMenu() {
E.showMenu({
"" : {title:/*LANG*/"Clock Background", back:back},
/*LANG*/"Mode" : {
value : settings.style,
onchange : showModeMenu
}
});
}
function showMainMenu() {
E.showMenu({
"" : {title:/*LANG*/"Clock Background", back:back},
/*LANG*/"Mode" : {
value : settings.style,
onchange : showModeMenu
},
/*LANG*/"View" : () => {
Bangle.setUI({mode:"custom",touch:showMainMenu,btn:showMainMenu});
require("clockbg").reload();
require("clockbg").fillRect(Bangle.appRect);
}
});
}
showMainMenu();
})
/* Scripts for generating colors. Change the values in HSBtoRGB to generate different effects
a = new Array(16);
a.fill(0);
g.clear();
w = Math.floor(g.getWidth()/a.length);
print(a.map((n,i)=>{
var j = i/(a.length-1); // 0..1
var c = E.HSBtoRGB(j,1,1,24); // rainbow
var c = E.HSBtoRGB(j,0.6,1,24); // faded rainbow
var c = E.HSBtoRGB(0.8, j,1,24); // purple->white
var c = E.HSBtoRGB(0.1, j,1,24); // blue->white
var c = E.HSBtoRGB(0.4, 1,j,24); // black->green
var col = c.toString(16).padStart(6,0).replace(/(.).(.).(.)./,"\"#$1$2$3\"");
g.setColor(eval(col)).fillRect(i*w,0, i*w+w-1,31);
return col;
}).join(","))
*/
showMainMenu();
})

View File

@ -12,3 +12,4 @@
0.30: Added options to show widgets and date on twist and tap. New fonts.
0.31: Bugfix, no more freeze.
0.32: Minor code improvements
0.33: Messages would sometimes halt the clock. This should be fixed now.

View File

@ -1,9 +1,6 @@
{
let drawTimeout;
let extrasTimeout;
let onLock;
//let onTap;
//let onTwist;
let extrasTimer=0;
let settings = require('Storage').readJSON("contourclock.json", true) || {};
if (settings.fontIndex == undefined) {
settings.fontIndex = 0;
@ -16,9 +13,9 @@
require('Storage').writeJSON("contourclock.json", settings);
}
require("FontTeletext10x18Ascii").add(Graphics);
let extrasShown = (!settings.hidewhenlocked) && (!Bangle.isLocked());
let installedFonts = require('Storage').readJSON("contourclock-install.json") || {};
if (installedFonts.n > 0) { //New install - check for unused font files
// New install - check for unused font files. This should probably be handled by the installer instead
if (installedFonts.n > 0) {
settings.fontIndex = E.clip(settings.fontIndex, -installedFonts.n + 1, installedFonts.n - 1);
require('Storage').writeJSON("contourclock.json", settings);
for (let n = installedFonts.n;; n++) {
@ -27,14 +24,22 @@
}
require("Storage").erase("contourclock-install.json");
}
let showExtras = function() { //show extras for a limited time
let onLock = function(locked) {if (!locked) showExtras();};
let showExtras = function() { //show extras for 5s
drawExtras();
if (extrasTimeout) clearTimeout(extrasTimeout);
extrasTimeout = setTimeout(() => {
extrasTimeout = undefined;
hideExtras();
}, 5000);
extrasShown = false;
extrasTimer = 5000-60000-(Date.now()%60000);
if (extrasTimer<0) { //schedule next redraw early to hide extras
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 5000);
}
};
let hideExtras = function() {
g.reset();
g.clearRect(0, 138, g.getWidth() - 1, 176);
if (settings.widgets) require("widget_utils").hide();
};
let drawExtras = function() { //draw date, day of the week and widgets
let date = new Date();
@ -43,38 +48,27 @@
g.setFont("Teletext10x18Ascii").setFontAlign(0, 1);
if (settings.weekday) g.drawString(require("locale").dow(date).toUpperCase(), g.getWidth() / 2, g.getHeight() - 18);
if (settings.date) g.drawString(require('locale').date(date, 1), g.getWidth() / 2, g.getHeight());
require("widget_utils").show();
extrasShown = true;
};
let hideExtras = function() {
if (extrasTimeout) clearTimeout(extrasTimeout);
extrasTimeout = undefined; //NEW
g.reset();
g.clearRect(0, 138, g.getWidth() - 1, 176);
require("widget_utils").hide();
extrasShown = false; ///NEW
if (settings.widgets) require("widget_utils").show();
};
let draw = function() {
if (drawTimeout) clearTimeout(drawTimeout); //NEW
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
if (extrasTimer>0) { //schedule next draw early to remove extras
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, extrasTimer);
extrasTimer=0;
} else {
if (settings.hideWhenLocked) hideExtras();
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
g.reset();
if (extrasShown) drawExtras();
else hideExtras();
if (!settings.hideWhenLocked) drawExtras();
require('contourclock').drawClock(settings.fontIndex);
};
if (settings.hideWhenLocked) {
onLock = locked => {
if (!locked) {
require("widget_utils").show();
drawExtras();
} else {
require("widget_utils").hide();
hideExtras();
}
};
Bangle.on('lock', onLock);
if (settings.tapToShow) Bangle.on('tap', showExtras);
if (settings.twistToShow) Bangle.on('twist', showExtras);
@ -82,14 +76,16 @@
Bangle.setUI({
mode: "clock",
remove: function() {
Bangle.removeListener('lock', onLock);
Bangle.removeListener('tap', showExtras);
Bangle.removeListener('twist', showExtras);
if (drawTimeout) clearTimeout(drawTimeout);
if (extrasTimeout) clearTimeout(extrasTimeout);
drawTimeout = undefined;
extrasTimeout = undefined;
if (settings.hideWhenLocked) require("widget_utils").show();
if (settings.hideWhenLocked) {
Bangle.removeListener('lock', onLock);
if (settings.tapToShow) Bangle.removeListener('tap', showExtras);
if (settings.twistToShow) Bangle.removeListener('twist', showExtras);
}
if (drawTimeout) {
clearTimeout(drawTimeout);
drawTimeout = undefined;
}
if (settings.hideWhenLocked && settings.widgets) require("widget_utils").show();
g.reset();
g.clear();
}
@ -97,7 +93,8 @@
g.clear();
if (settings.widgets) {
Bangle.loadWidgets();
setTimeout(Bangle.drawWidgets,0); //NEW
Bangle.drawWidgets();
}
draw();
if (!settings.hideWhenLocked || !Bangle.isLocked()) showExtras();
}

View File

@ -1,7 +1,7 @@
{ "id": "contourclock",
"name": "Contour Clock",
"shortName" : "Contour Clock",
"version": "0.32",
"version": "0.33",
"icon": "app.png",
"readme": "README.md",
"description": "A Minimalist clockface with large Digits.",

View File

@ -2,3 +2,4 @@
0.02: Added decrement and touch functions
0.03: Set color - ensures widgets don't end up coloring the counter's text
0.04: Adopted for BangleJS 2
0.05: Support translations

View File

@ -95,9 +95,9 @@ if (BANGLEJS2) {
g.clear(1).setFont("6x8");
g.setBgColor(g.theme.bg).setColor(g.theme.fg);
if (BANGLEJS2) {
g.drawString('Swipe up to increase\nSwipe down to decrease\nPress button to reset.', x, 100 + y);
g.drawString([/*LANG*/"Swipe up to increase", /*LANG*/"Swipe down to decrease", /*LANG*/"Press button to reset"].join("\n"), x, 100 + y);
} else {
g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', x, 100 + y);
g.drawString([/*LANG*/"Tap right or BTN1 to increase", /*LANG*/"Tap left or BTN3 to decrease", /*LANG*/"Press BTN2 to reset"].join("\n"), x, 100 + y);
}
Bangle.loadWidgets();

View File

@ -1,7 +1,7 @@
{
"id": "counter",
"name": "Counter",
"version": "0.04",
"version": "0.05",
"description": "Simple counter",
"icon": "counter_icon.png",
"tags": "tool",

View File

@ -1,3 +1,4 @@
0.01: Initial release.
0.02: Fix reset of progress bars on midnight. Fix display of 100k+ steps.
0.03: Added option to display weather.
0.04: Added option to display live updates of step count.

View File

@ -20,12 +20,13 @@ The appearance is highly configurable. In the settings menu you can:
- Set the daily step goal.
- En- or disable the individual progress bars.
- Set if your week should start with Monday or Sunday (for week progress bar).
- Toggle live step count updates.*
*) Hiding seconds should further reduce power consumption as the draw interval is prolonged as well.
*) Hiding seconds and leaving live steps off should further reduce power consumption as the draw interval is prolonged as well.
The clock implements Fast Loading for faster switching to and fro.
## Contributors
- [tinxx](https://github.com/tinxx)
- [peerdavid](https://github.com/peerdavid)

View File

@ -14,6 +14,7 @@
weekBar: true,
mondayFirst: true,
dayBar: true,
liveSteps: false,
}, require('Storage').readJSON('edgeclk.settings.json', true) || {});
/* Runtime Variables
@ -279,6 +280,9 @@
drawLower();
};
const onStep = function () {
drawSteps();
}
/* Lifecycle Functions
------------------------------------------------------------------------------*/
@ -298,6 +302,9 @@
// Charging event signals when charging status changes:
Bangle.on('charging', onCharging);
// Continously update step count when they happen:
if (settings.redrawOnStep) Bangle.on('step', onStep);
};
const deregisterEvents = function () {
@ -306,6 +313,7 @@
Bangle.removeListener('health', onHealth);
Bangle.removeListener('lock', onLock);
Bangle.removeListener('charging', onCharging);
if (settings.redrawOnStep) Bangle.removeListener('step', onStep);
};
const startTimers = function () {

View File

@ -2,7 +2,7 @@
"id": "edgeclk",
"name": "Edge Clock",
"shortName": "Edge Clock",
"version": "0.03",
"version": "0.04",
"description": "Crisp clock with perfect readability.",
"readme": "README.md",
"icon": "app.png",

View File

@ -14,6 +14,7 @@
weekBar: true,
mondayFirst: true,
dayBar: true,
redrawOnStep: false,
};
const saved_settings = storage.readJSON(SETTINGS_FILE, true);
@ -121,5 +122,12 @@
save();
},
},
'Live steps': {
value: settings.redrawOnStep,
onchange: () => {
settings.redrawOnStep = !settings.redrawOnStep;
save();
},
},
});
})

BIN
apps/exactwords/0100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
apps/exactwords/0634.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
apps/exactwords/1200.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
apps/exactwords/1517.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
apps/exactwords/1616.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
apps/exactwords/2020.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
apps/exactwords/2358.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1 @@
0.1: New App! Need to work out locale settings

38
apps/exactwords/README.md Normal file
View File

@ -0,0 +1,38 @@
# Exact Words
This is a clock for expressing the time in exact words. Each minute of
the day has a different phrase.
Ranging from "Twelve" to "Coming up to midnight" to "A little after
twenty-five past four in the early hours"
Screenshots best demonstrate
![1200.png](1200.png)
![2358.png](2358.png)
![1616.png](1616.png)
![0634.png](0634.png)
![1517.png](1517.png)
![2020.png](2020.png)
"just gone " - as in Just gone quarter past four is 16:16
"a little after ", as in A little after quarter past three is 15:17
"coming up to ", as in Coming up to midnight is 23:58
"almost " as in Almost twenty-five to seven is 06:34
## To Do
Add localisation.
## Requests
Written by: [Brendan Sleight](https://github.com/bmsleight/) For support and discussion please post in the Bangle JS Forum
## Creator
[Brendan Sleight](https://github.com/bmsleight/)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/AAMEjtogFQgEMjIFB6EAh0d/eH7dwAoNrx/X4Eaju7g/boAoKkACFh9f23BzswAoO38P/0EP78/wN/0EO70WwPf2EGDYKNCguAgFAlAFDgAFBg/d3F4v3ggvz/F8lXgg/x3F8nXAPBkHEgQABn+Xs1MAoN9393/wFBqu+r++AoN021W9ytYQIQACv3/j/bz+cv+/j/0/8cgECI4MQPYWUqoYCgP//4fDiAQCAAoA="))

225
apps/exactwords/app.js Normal file
View File

@ -0,0 +1,225 @@
// timeout used to update every minute
var drawTimeout;
// https://www.espruino.com/Bangle.js+Locale
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
function wordsFromTime(h, m)
{
// Tests
/*
// Example 12:00 = Twelve
// h = 12;
// m = 0;
// Example 23:58 = Coming up to midnight
// h = 23;
// m = 58;
// Example 12:15 = Quarter past twelve
// h = 12;
// m = 15;
// Example 04:16 = Just gone quarter past four
// h = 16;
// m = 16;
// Example 01:00 = One at night
// h = 1;
// m = 0;
// Example 17:01 = Just gone five in the afternoon
// h = 17;
// m = 1;
// Example 05:25 = Twenty-five past five in the early hours
// h = 23;
// m = 33;
// Example 22:33 = coming up to eleven at night
// max
//words = "a little after twenty-five past four in the early hours";
// h = 04;
// m = 27;
*/
const HOUR_WORD_ARRAY = [
"midnight", "one", "two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten", "eleven", "twelve", "one", "two", "three",
"four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
"midnight"];
const PART_DAY_WORD_ARRAY = ["",
" at night",
" in the early hours",
" in the early hours",
" in the early hours",
" in the early hours",
" in the morning",
" in the morning",
" in the morning",
" in the morning",
" in the morning",
" in the morning",
"",
" in the afternoon",
" in the afternoon",
" in the afternoon",
" in the afternoon",
" in the afternoon",
" in the evening",
" in the evening",
" in the evening",
" in the evening",
" at night",
" at night",
""];
const MINUTES_ROUGH_ARRAY = ["",
"five past ",
"ten past ",
"quarter past ",
"twenty past ",
"twenty-five past ",
"half past ",
"twenty-five to ",
"twenty to ",
"quarter to ",
"ten to ",
"five to ",
""];
const MINUTES_ACCURATE_ARRAY = ["", "just gone ", "a little after ", "coming up to ", "almost "];
var hourAdjusted = h;
var words = " ", hourWord = " ", partDayWord = " ", minutesRough = " ", minutesAccurate = " ";
// At 33 past the hours we start referign to the next hour
if (m > 32) {
hourAdjusted = (h+ 1) % 24;
} else {
hourAdjusted = h;
}
hourWord = HOUR_WORD_ARRAY[hourAdjusted];
partDayWord = PART_DAY_WORD_ARRAY[Math.round(hourAdjusted)];
minutesRough = MINUTES_ROUGH_ARRAY[Math.round((m + 0 ) / 5)];
minutesAccurate = MINUTES_ACCURATE_ARRAY[m % 5];
words = minutesAccurate + minutesRough + hourWord + partDayWord;
words = words.charAt(0).toUpperCase() + words.slice(1);
return words;
}
function wordsFromDayMonth(day, date, month)
{
// Tests
// Example 12:00 = Twelve
// New Year's Day
// date = 1;
// month = 0;
// on the Ides of March
// date = 15;
// month = 2;
// , ERROR C Nonsense in BASIC
// date = 1;
// month = 3;
// - O'Canada
// date = 1;
// month = 6;
// - on Halloween
// date = 31;
// month = 9;
// - Christmas Eve
// date = 24;
// month = 11;
// - Christmas Day
// date = 25;
// month = 11;
// - Boxing day
// date = 26;
// month = 11;
// New Year's eve
// date = 31;
// month = 11;
// longest
// date = 29;
// month = 10;
const DAY_WORD_ARRAY = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const DATE_WORD_ARRAY = ["zero", "first", "second", "third", "fourth", "fifth", "sixth", "seventh","eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth","sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth", "twenty-first", "twenty-second", "twenty-third","twenty-fourth", "twenty-fifth", "twenty-sixth", "twenty-seventh", "twenty-eighth", "twenty-ninth", "thirtieth", "thirty-first"];
const MONTH_WORD_ARRAY = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var words = " ";
words = DAY_WORD_ARRAY[day] + ", " + DATE_WORD_ARRAY[date] + " of " + MONTH_WORD_ARRAY[month];
if ((date == 1) && (month == 0)) {
words = "New Year's Day";
} else if ((date == 15) && (month == 2)) {
words = DAY_WORD_ARRAY[day] + " on the Ides of March";
} else if ((date == 1) && (month == 3)) {
words = DAY_WORD_ARRAY[day] + ", ERROR C Nonsense in BASIC";
} else if ((date == 1) && (month == 6)) {
words = DAY_WORD_ARRAY[day] + " - O'Canada";
} else if ((date == 31) && (month == 9)) {
words = DAY_WORD_ARRAY[day] + " - on Halloween";
} else if ((date == 24) && (month == 11)) {
words = "Christmas Eve";
} else if ((date == 25) && (month == 11)) {
words = "Christmas Day";
} else if ((date == 26) && (month == 11)) {
words = "Boxing Day";
} else if ((date == 31) && (month == 11)) {
words = "New Year's eve";
}
return words;
}
function draw() {
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var day = d.getDay();
var date = d.getDate();
var month = d.getMonth();
var timeStr = wordsFromTime(h,m);
var dateStr = wordsFromDayMonth(day, date, month);
// draw time
g.setBgColor(g.theme.bg);
g.setColor(g.theme.fg);
g.clear();
g.setFontAlign(0,0).setFont("Vector",24);
g.drawString(g.wrapString(timeStr, g.getWidth()).join("\n"),x,y-24*0);
// draw date
g.setFontAlign(0,0).setFont("Vector",12);
g.drawString(g.wrapString(dateStr, g.getWidth()).join("\n"),x,y+12*6);
// queue draw in one minute
queueDraw();
}
// Clear the screen once/, at startup
g.clear();
// draw immediately at first, queue update
draw();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();

BIN
apps/exactwords/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,22 @@
{ "id": "exactwords",
"name": "Exact Words Clock",
"shortName":"Exact Words",
"version":"0.1",
"description": "Each minute of the day has a different phrase. ",
"icon": "app.png",
"screenshots" : [ { "url":"1517.png" },
{ "url":"0634.png" },
{ "url":"1200.png" },
{ "url":"1517.png" },
{ "url":"1616.png" },
{ "url":"2020.png" },
{ "url":"2358.png" } ],
"tags": "clock",
"type": "clock",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"exactwords.app.js","url":"app.js"},
{"name":"exactwords.img","url":"app-icon.js","evaluate":true}
]
}

1
apps/factclock/ChangeLog Normal file
View File

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

5
apps/factclock/README.md Normal file
View File

@ -0,0 +1,5 @@
# Fact Clock
A clock that displays a random fact alongside the time.
This uses `text_facts` for the list of facts, but you can implement new apps that provide the `textsource` module (see the readme for the `text_facts` app) to make this clock display different information.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwcF23btoCV7QfBrYRM2kAhMkgEtCJegiwDBgBBLtAMCoA3BCwQRHoADCNIUCKxMDAYPAPgZcICIWwRwgRI6ARBwAREhYRIjYRBgVggEDCJOACIO07dsgETtsBCJcTsEGBIORCJRHCmwJB2LRHg0DGQIFC7ZKBNwnRkmSm0ACIsAgO2yRcCwIRDhgRILgWJCIMWgENR4fbhuBCINBMgMJCJNt2YLBoEtCIcGwEN2wRE3+kCI1C4DpDR4WdGoIRFpHYCIUBR4XtCJFYCIacBtgRIpKECCIdnpJZHpMhZIRcB7c7AwIRHkktEYNN23ZaYQRIwDhDrwRLyUCBoOXCoYRJpMgIgIIECJMkEAKwBCJ41OCMoFECJlpNaJZ0I/0BCKEACIsAn//AAX0yUQCIQAGFQ2QCJMACIuAlu2wASIAAkBJYPQBIsF0AHFhZgEARoA=="))

110
apps/factclock/app.js Normal file
View File

@ -0,0 +1,110 @@
Graphics.prototype.setFontBebasNeue = function() {
// Actual height 31 (32 - 2)
// 1 BPP
return this.setFontCustom(
atob('AAAAAAAAAAAAAAAAAAAPgAAAAAD4AAAAAA+AAAAAAPgAAAAAD4AAAAAAAAAAAAAAgAAAAAA4AAAAAB+AAAAAD/gAAAAD/4AAAAH/4AAAAH/4AAAAP/wAAAAP/wAAAAf/gAAAAf/gAAAA//AAAAA/+AAAAAP+AAAAAD8AAAAAA8AAAAAAIAAAAAAAAAAAAAAAAAAAAAAf//8AAAf///wAAP///+AAH////wAD////+AA/////gAPgAAD4AD4AAA+AA+AAAPgAPgAAD4AD////+AAf////AAH////wAA////4AAH///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAADwAAAAAA8AAAAAAfAAAAAAHwAAAAAD////4AB////+AA/////gAP////4AD////+AA/////gAAAAAAAAAAAAAAAAAcAAHgAB/gA/4AA/4A/+AAf+Af/gAP/gP/4AD/wH/+AA+AD+PgAPgD+D4AD4B/A+AA+B/gPgAP//wD4AD//4A+AAf/8APgAD/8AD4AAf8AAeAAAAAAAAAAAAAAAAADgAfAAAH4AH8AAD+AB/wAB/gAf8AA/4AH/gAPwPgH4AD4D4A+AA+A+APgAPgPwD4AD8H+B+AA/////gAH////wAB//f/8AAP/j/+AAA/gf+AAAAAAAAAAAAAHgAAAAAP8AAAAAP/AAAAAP/wAAAAf/8AAAAf//AAAA//HwAAA/+B8AAA/+AfAAA/+AHwAAP////4AD////+AA/////gAP////4AD////+AAAAAHwAAAAAB8AAAAAAAAAAAAAAAAAAAAh4AAD//4fwAA//+H+AAP//h/wAD//4f+AA//+B/gAPgeAD4AD4PAA+AA+DwAPgAPh+AD4AD4f//+AA+D///gAPg///wAD4H//4AA8A//8AAAAAAAAAAAAAAAAAA///gAAB////AAA////4AAf////AAP////4AD////+AA+A8APgAPgfAD4AD4HwA+AA+B8APgAP8f//4AD/H//+AAfx///AAD8P//gAAfB//wAAAAB/AAAAAAAAAAAAAAAAAA8AAAAAAPgAAAAAD4AAACAA+AAAHgAPgAAf4AD4AA/+AA+AD//gAPgH//4AD4P//wAA+///gAAP//+AAAD//8AAAA//wAAAAP/AAAAAD+AAAAAAAAAAAAAAAAAAAAAH4D/gAAH/j/+AAD/9//wAB////8AA/////gAP9/4H4AD4D8A+AA+A+APgAPgPgD4AD4D8A+AA/////gAP////4AB////8AAP/3/+AAB/4f/AAAAAA+AAAAAAAAAAAAAAAAAAH/wHAAAH//B8AAH//4fwAB///H8AA///x/gAPwH8H4AD4AfA+AA+AHwPgAPgB4D4AD4A+B+AA/////gAH////wAB////8AAP///+AAA///+AAAAAAAAAAAAAAAAAAAAAAAAAAPgA+AAAD4APgAAA+AD4AAAPgA+AAAD4APgAAAAAAAAA'),
46,
atob("CBIRDxEREhESERIRCA=="),
44|65536
);
};
{
// the font we're using
const factFont = "6x15";
// swap every 10 minutes
const minsPerFact = 5;
// timeout used to update every minute
let drawTimeout;
// the fact we're going to display (pre-rendered with a border)
let factGfx;
// how long until the next fact?
let factCounter = minsPerFact;
// the gfx we use for the time so we can gat a shadow on it
let timeGfx = Graphics.createArrayBuffer(g.getWidth()>>1, 48, 2, {msb:true});
timeGfx.transparent = 0;
timeGfx.palette = new Uint16Array([
0, g.toColor(g.theme.bg), 0, g.toColor(g.theme.fg)
]);
let getNewFact = () => {
let fact = require("textsource").getRandomText();
// wrap to fit the screen
let lines = g.setFont(factFont).wrapString(fact.txt, g.getWidth()-10);
let txt = lines.join("\n");
// allocate a gfx for this
factGfx = Graphics.createArrayBuffer(g.getWidth(), g.stringMetrics(txt).height+4, 2, {msb:true});
factGfx.transparent = 0;
factGfx.setFont(factFont).setFontAlign(0,-1).setColor(3).drawString(txt, factGfx.getWidth()/2, 2);
if (factGfx.filter) factGfx.filter([ // add shadow behind text
0,1,1,1,0,
1,1,1,1,1,
1,1,1,1,1,
1,1,1,1,1,
0,1,1,1,0,
], { w:5, h:5, div:1, max:1, filter:"max" });
factGfx.palette = new Uint16Array([
0, g.toColor(g.theme.bg), 0, g.toColor(g.theme.fg)
]);
};
getNewFact();
// schedule a draw for the next minute
let queueDraw = function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
let draw = function() {
// queue next draw in one minute
queueDraw();
// new fact?
if (--factCounter < 0) {
factCounter = minsPerFact;
getNewFact();
}
// Work out where to draw...
g.reset();
require("clockbg").fillRect(Bangle.appRect);
// work out locale-friendly date/time
var date = new Date();
var timeStr = require("locale").time(date,1);
var dateStr = require("locale").date(date,1);
// draw time to buffer
timeGfx.clear(1);
timeGfx.setFontAlign(0,-1).setFont("BebasNeue");
timeGfx.drawString(timeStr,timeGfx.getWidth()/2,2);
timeGfx.setFontAlign(0,1).setFont("6x8");
timeGfx.drawString(dateStr,timeGfx.getWidth()/2,timeGfx.getHeight()-2);
// add shadow to buffer and render
if (timeGfx.filter) timeGfx.filter([ // add shadow behind text
0,1,1,1,0,
1,1,1,1,1,
1,1,1,1,1,
1,1,1,1,1,
0,1,1,1,0,
], { w:5, h:5, div:1, max:1, filter:"max" });
var y = (Bangle.appRect.y+g.getHeight()-(factGfx.getHeight()+timeGfx.getHeight()*2))>>1;
g.drawImage(timeGfx,0, y, {scale:2});
// draw the fact
g.drawImage(factGfx,0, g.getHeight()-factGfx.getHeight());
};
// Show launcher when middle button pressed
Bangle.setUI({mode:"clock", remove:function() {
// free any memory we allocated to allow fast loading
delete Graphics.prototype.setFontBebasNeue;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
require('widget_utils').show(); // re-show widgets
}});
// Load widgets
Bangle.loadWidgets();
require("widget_utils").swipeOn();
// draw immediately at first, queue update
draw();
}

BIN
apps/factclock/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,17 @@
{ "id": "factclock",
"name": "Fact Clock",
"shortName":"Facts",
"version":"0.01",
"description": "A clock that displays a random fact alongside the time",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"dependencies" : { "textsource":"module", "clockbg":"module" },
"readme": "README.md",
"storage": [
{"name":"factclock.app.js","url":"app.js"},
{"name":"factclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Corrected formatting of punctuation
0.02: Corrected formatting of punctuation
0.03: Extend range slightly to include Ukrainian fonts

Binary file not shown.

View File

@ -1,6 +1,6 @@
{ "id": "fontext",
"name": "Fonts (150+ languages)",
"version":"0.02",
"version":"0.03",
"description": "Installs a font containing 1000 Unifont characters, which should handle the majority of non-Chinese/Japanese/Korean languages (only 20kb)",
"icon": "app.png",
"tags": "font,fonts,language",

1
apps/gbdiscon/ChangeLog Normal file
View File

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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwYHEgMkyVAkmQDJYREyQRRoARQpARQpIRRkARNggRBkgRNgARCwARNiQRBSRIREgQRBSRIREgARCSRARFhKSKCIoFCSRAjISQ0BAQJZHCI6ZBTwKPEI44tBTIMSYoZ9IBIYyEWZCHEKwbXIDwZ6MBghjBWBR7DIQbmJAAJ7BexYRHGZZHEchRrGNJYRIRpARJWI7XDCIrVHLIeACIpuIgKwBR4RcQyDLFCJbLGCJcAZZgLEiRcLCIkCZZYvFCKAjDI6BZOPqD+PWaUJa6ARCTxARICBQRFPRIRHPRIRHBg4A="))

7
apps/gbdiscon/app.js Normal file
View File

@ -0,0 +1,7 @@
{
Bangle.setUI({mode:"custom",remove:()=>{}});"Bangle.loadWidgets"; // Allow fastloading.
Bluetooth.println(JSON.stringify({t:"intent", action:"nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_DISCONNECT", extra:{EXTRA_DEVICE_ADDRESS:NRF.getAddress()}}));
Bangle.showClock();
}

BIN
apps/gbdiscon/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,13 @@
{ "id": "gbdiscon",
"name": "Disconnect from Gadgetbridge",
"shortName":"Disconnect Gadgetbridge",
"version":"0.01",
"description": "Disconnect from your android device by running this app. The app will forward you to your clock face immediately after triggering the command. (Gadgetbridge nightly required until version 82 is released)",
"icon": "app.png",
"tags": "android, gadgetbridge, bluetooth, bt",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"gbdiscon.app.js","url":"app.js"},
{"name":"gbdiscon.img","url":"app-icon.js","evaluate":true}
]
}

4
apps/hadash/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
hadash.json
node_modules
package-lock.json
package.json

2
apps/hadash/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
1.00: initial release
1.01: remember scoll positions of menus; "silent" option for service calls

66
apps/hadash/README.md Normal file
View File

@ -0,0 +1,66 @@
# Home-Assistant Dashboard
This app interacts with a Home-Assistant (HA) instance. You can query entity
states and call services. This allows you access to up-to-date information of
any home automation system integrated into HA, and you can also control your
automations from your wrist.
![](screenshot.png)
## How It Works
This app uses the REST API to directly interact with HA (which requires a
"long-lived access token" - refer to "Configuration").
You can define a menu structure to be displayed on your Bangle, with the states
to be queried and services to be called. Menu entries can be:
* entry to show the state of a HA entity
* entry to call a HA service
* sub-menus, including nested sub-menus
Calls to a service can also have optional input for data fields on the Bangle
itself.
## Configuration
After installing the app, use the "interface" page (floppy disk icon) in the
App Loader to configure it.
Make sure to set the "Home-Assistant API Base URL" (which must include the
"/api" path, as well - but no slash at the end).
Also create a "long-lived access token" in HA (under the Profile section, at
the bottom) and enter it as the "Long-lived access token".
The tricky bit will be to configure your menu structure. You need to have a
basic understanding of the JSON format. The configuration page uses a JSON
Editor which will check the syntax and highlight any errors for you. Follow the
instructions on the page regarding how to configure menus, menu entries and the
required attributes. It also contains examples.
Once you're happy with the menu structure (and you've entered the base URL and
access token), click the "Configure / Upload to Bangle" button.
## Security
The "long-lived access token" will be stored unencrypted on your Bangle. This
would - in theory - mean that if your Bangle gets stolen, the new "owner" would
have unrestricted access to your Home-Assistant instance (the thief would have
to be fairly tech-savvy, though). However, I suggest you create a separate
token exclusively for your Bangle - that way, it's very easy to simply delete
that token in case your watch is stolen or lost.
## To-Do
- A better way to configure the menu structure would be useful, something like a custom editor (replacing the jsoneditor).
## Author
Flaparoo [github](https://github.com/flaparoo)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwgmjhGACyuIxAYUCwIABFyowUCwYwSFwgwSCwowQFwwwQCw4wOFxAwOCxIwMFwuDnAwPCwv//4YFFx0/C4PzGBpXFCwIABMJiMGC5IwGQ4wXJGAq7HC5QwEW4+PCwP4YZTqJFxAwEdBIXKGAQXWIxIXMwAXXBRIXFwYFBnATKC5E/AoPzC6bdKC/4XTx4WCO5CbGC4YuDU5CbGC4QuCma+JKYwECXhoXIFwTsLC5DrONgxzMO4woDFx6nDFIYuPJQoqBFx4ADRIYuSGAiUEGCQXUYg4wUC6YwDBpUIGBYWJwA"))

247
apps/hadash/hadash.app.js Normal file
View File

@ -0,0 +1,247 @@
/*
* Home-Assistant Dashboard - Bangle.js
*/
const APP_NAME = 'hadash';
var scroller;
// Load settings
var settings = Object.assign({
menu: [
{ type: 'state', title: 'Check for updates', id: 'update.home_assistant_core_update' },
{ type: 'service', title: 'Create Notification', domain: 'persistent_notification', service: 'create',
data: { 'message': 'test notification', 'title': 'Test'} },
{ type: 'menu', title: 'Sub-menu', data:
[
{ type: 'state', title: 'Check for Supervisor updates', id: 'update.home_assistant_supervisor_update' },
{ type: 'service', title: 'Restart HA', domain: 'homeassistant', service: 'restart', silent: true, data: {} }
]
},
{ type: 'service', title: 'Custom Notification', domain: 'persistent_notification', service: 'create',
data: { 'title': 'Not via input'},
input: { 'message': { options: [], value: 'Pre-filled text' },
'notification_id': { options: [ 123, 456, 136 ], value: 999, label: "ID" } } },
],
HAbaseUrl: '',
HAtoken: '',
}, require('Storage').readJSON(APP_NAME+'.json', true) || {});
// wrapper to show a menu (preserving scroll position)
function showScrollerMenu(menu) {
const r = E.showMenu(menu).scroller;
scroller = r;
return r;
}
// query an entity state
function queryState(title, id, level) {
menus[level][''].scroll = scroller.scroll;
E.showMessage('Fetching entity state from HA', { title: title });
Bangle.http(settings.HAbaseUrl+'/states/'+id, {
headers: {
'Authorization': 'Bearer '+settings.HAtoken,
'Content-Type': 'application/json'
},
}).then(data => {
//console.log(data);
let HAresp = JSON.parse(data.resp);
let title4prompt = title;
let msg = HAresp.state;
if ('attributes' in HAresp) {
if ('friendly_name' in HAresp.attributes)
title4prompt = HAresp.attributes.friendly_name;
if ('unit_of_measurement' in HAresp.attributes)
msg += HAresp.attributes.unit_of_measurement;
}
E.showPrompt(msg, { title: title4prompt, buttons: {OK: true} }).then((v) => { showScrollerMenu(menus[level]); });
}).catch( error => {
console.log(error);
E.showPrompt('Error querying state!', { title: title, buttons: {OK: true} }).then((v) => { showScrollerMenu(menus[level]); });
});
}
// call a service
function callService(title, domain, service, data, level, silent) {
menus[level][''].scroll = scroller.scroll;
E.showMessage('Calling HA service', { title: title });
Bangle.http(settings.HAbaseUrl+'/services/'+domain+'/'+service, {
method: 'POST',
body: data,
headers: {
'Authorization': 'Bearer '+settings.HAtoken,
'Content-Type': 'application/json'
},
}).then(data => {
//console.log(data);
if (! silent) {
return E.showPrompt('Service called successfully', { title: title, buttons: {OK: true} });
}
}).then(() => {
showScrollerMenu(menus[level]);
}).catch( error => {
console.log(error);
E.showPrompt('Error calling service!', { title: title, buttons: {OK: true} }).then((v) => { showScrollerMenu(menus[level]); });
});
}
// callbacks for service input menu entries
function serviceInputChoiceChange(v, key, entry, level) {
entry.input[key].value = entry.input[key].options[v];
getServiceInputData(entry, level);
}
function serviceInputFreeform(key, entry, level) {
require("textinput").input({text: entry.input[key].value}).then(result => {
entry.input[key].value = result;
getServiceInputData(entry, level);
});
}
// get input data before calling a service
function getServiceInputData(entry, level) {
menus[level][''].scroll = scroller.scroll;
let serviceInputMenu = {
'': {
'title': entry.title,
'back': () => showScrollerMenu(menus[level])
},
};
let CBs = {};
for (let key in entry.input) {
// pre-fill data with default values
if ('value' in entry.input[key])
entry.data[key] = entry.input[key].value;
let label = ( ('label' in entry.input[key] && entry.input[key].label) ? entry.input[key].label : key );
let key4CB = key;
if ('options' in entry.input[key] && entry.input[key].options.length) {
// give choice from a selection of options
let idx = -1;
for (let i in entry.input[key].options) {
if (entry.input[key].value == entry.input[key].options[i]) {
idx = i;
}
}
if (idx == -1) {
idx = entry.input[key].options.push(entry.input[key].value) - 1;
}
// the setTimeout method can not be used for the "format" CB since it expects a return value:
CBs[`${key}_format`] = ((key) => function(v) { return entry.input[key].options[v]; })(key);
serviceInputMenu[label] = {
value: parseInt(idx),
min: 0,
max: entry.input[key].options.length - 1,
format: CBs[key+'_format'],
onchange: (v) => setTimeout(serviceInputChoiceChange, 10, v, key4CB, entry, level)
};
} else {
// free-form text input
serviceInputMenu[label] = () => setTimeout(serviceInputFreeform, 10, key4CB, entry, level);
}
}
// menu entry to actually call the service:
serviceInputMenu['Call service'] = function() { callService(entry.title, entry.domain, entry.service, entry.data, level, entry.silent); };
E.showMenu(serviceInputMenu);
}
// menu hierarchy
var menus = [];
// add menu entries
function addMenuEntries(level, entries) {
for (let i in entries) {
let entry = entries[i];
let entryCB;
// is there a menu entry title?
if (! ('title' in entry) || ! entry.title)
entry.title = 'TBD';
switch (entry.type) {
case 'state':
/*
* query entity state
*/
if ('id' in entry && entry.id) {
entryCB = () => setTimeout(queryState, 10, entry.title, entry.id, level);
}
break;
case 'service':
/*
* call HA service
*/
if (! ('silent' in entry))
entry.silent = false;
if ('domain' in entry && entry.domain && 'service' in entry && entry.service) {
if (! ('data' in entry))
entry.data = {};
if ('input' in entry) {
// get input for some data fields first
entryCB = () => setTimeout(getServiceInputData, 10, entry, level);
} else {
// call service straight away
entryCB = () => setTimeout(callService, 10, entry.title, entry.domain, entry.service, entry.data, level, entry.silent);
}
}
break;
case 'menu':
/*
* sub-menu
*/
entryCB = () => setTimeout(showSubMenu, 10, level + 1, entry.title, entry.data);
break;
}
// only attach a call-back to menu entry if it's properly configured
if (! entryCB) {
menus[level][entry.title + ' - not correctly configured!'] = {};
} else {
menus[level][entry.title] = entryCB;
}
}
}
// create and show a sub menu
function showSubMenu(level, title, entries) {
menus[level - 1][''].scroll = scroller.scroll;
menus[level] = {
'': {
'title': title,
'back': () => showScrollerMenu(menus[level - 1])
},
};
addMenuEntries(level, entries);
showScrollerMenu(menus[level]);
}
/*
* create the main menu
*/
menus[0] = {
'': {
'title': 'HA-Dash',
'back': () => load()
},
};
addMenuEntries(0, settings.menu);
// check required configuration
if (! settings.HAbaseUrl || ! settings.HAtoken) {
E.showAlert('The app is not yet configured!', 'HA-Dash').then(() => showScrollerMenu(menus[0]));
} else {
showScrollerMenu(menus[0]);
}

BIN
apps/hadash/hadash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

261
apps/hadash/interface.html Normal file
View File

@ -0,0 +1,261 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div class="form-group">
<label class="form-label" for="HAbaseUrl">Home-Assistant API Base URL:</label>
<input class="form-input" type="text" id="HAbaseUrl" placeholder="https://ha.example:8123/api" />
<div>
<small class="text-muted">Make sure to include "/api" as the URL path, but no slash at the end.</small>
</div>
<div class="form-group">
<label class="form-label" for="HAtoken">Long-lived access token:</label>
<input class="form-input" type="text" id="HAtoken" placeholder="Your Long-lived Access Token" />
<div>
<small class="text-muted">It's recommended to create a dedicated token for your Bangle.</small>
</div>
</div>
<div class="divider"></div>
<h4>Menu structure</h4>
<p>Use the editor below to configure the menu structure displayed in the
Bangle app. It is in the JSON format.</p>
<p>The main menu, and any sub-menus, are arrays. They can contain 3
different types of entries (objects) defined by the "type" attribute:</p>
<ul>
<li>
<b>Query an Entity State</b>
<pre class="code" data-lang="JSON">
{
"type": "state",
"title": "Menu entry title",
"id": "HA Entity ID"
}
</pre>
The required Entity ID can be looked up in HA under Settings -&gt;
Devices &amp; Services -&gt; Entities. For example:
<pre class="code" data-lang="JSON">
{
"type": "state",
"title": "Check for updates",
"id": "update.home_assistant_core_update"
}
</pre>
</li>
<li>
<b>Call a HA service</b>
<pre class="code" data-lang="JSON">
{
"type": "service",
"title": "Menu entry title",
"domain": "HA Domain",
"service": "HA Service",
"silent": (true|false),
"data": {
"key": "value"
}
}
</pre>
<p>The required information to call a HA service can be found in HA
under the Developer tools -&gt; Services. Use the "Go to YAML Mode"
function to see the actual names and values. The domain and service
parts are the 2 parts of the service name which are separated by a dot.
If the optional "silent" boolean is set to true (false is default),
there will be no message in case of a successful call. Any (optional)
data key/value pairs can be added under the "data" field. For example,
here's a service call YAML:
<pre class="code" data-lang="YAML">
service: persistent_notification.create
data:
message: test Notification
title: Test
</pre>
The resulting menu entry (JSON object) should be:
<pre class="code" data-lang="JSON">
{
"type": "service",
"title": "Create Notification",
"domain": "persistent_notification",
"service": "create",
"data": {
"message": "test Notification",
"title": "Test"
}
}
</pre>
If the service requires a target, include the
"entity_id"/"device_id"/etc. (listed under "target:") also as "data"
key/value pairs. For example, if the YAML also includes:
<pre class="code" data-lang="YAML">
target:
device_id: abcd1234
</pre>
... add another "data" key/value pair: <code>"device_id": "abcd1234"</code>.
If that doesn't work, list the device (or entity) ID in an array:
<code>"device_id": [ "abcd1234" ]</code></p>
<p>Data fields can also have variable input on the Bangle. In that
case, don't add the key/value pair under "data", but create an "input"
object with entries per "data" key:
<pre class="code" data-lang="JSON">
{
"key": {
"options": [],
"value": "",
"label": ""
}
}
</pre>
If "options" is left empty, the preferred text-input (pick your
favourite app - I like the dragboard) is called to allow entering a
free-form value. Otherwise, list the allowed values in the "options"
array. The "value" is the pre-defined/default value. The "label" is
optional and can be used to override the label for this key (as
displayed on the Bangle). For example:
<pre class="code" data-lang="JSON">
{
"type": "service",
"title": "Custom Notification",
"domain": "persistent_notification",
"service": "create",
"data": {
"title": "Fixed"
},
"input": {
"message": {
"options": [],
"value": "Pre-filled text"
},
"notification_id": {
"options": [
"123",
"456",
"136"
],
"value": "999",
"label": "ID"
}
}
}
</pre>
In the above example, the "data" will have 3 key/value pairs when the
service is called: the "title" is always the same, "message" can be
entered via the (free-form) text-input and "notification_id" can be
selected from a list of numbers (however, the prompt will be for "ID"
and not "notification_id"). If the default value is not listed in
"options" (like "999"), it will be added to that list.</p>
</li>
<li>
<b>Sub-menu</b>
<pre class="code" data-lang="JSON">
{
"type": "menu",
"title": "Menu entry / sub-menu title",
"data": []
}
</pre>
The "data" needs to be another array of menu entries. For example:
<pre class="code" data-lang="JSON">
{
"type": "menu",
"title": "Sub-menu",
"data": [
{
"type": "state",
"title": "Check for Supervisor updates",
"id": "update.home_assistant_supervisor_update"
},
{
"type": "service",
"title": "Restart HA",
"domain": "homeassistant",
"service": "restart",
"data": {}
}
]
}
</pre>
Sub-menus can contain other sub-menus, so you can have multiple levels
of nested menus.
</li>
</ul>
<div id="jsoneditor"></div>
<div class="divider"></div>
<p><div id="status"></div></p>
<button id="upload" class="btn btn-primary">Configure / Upload to Bangle</button>
<script>
var JSONEditorInstance;
var JSONEditor_target = document.getElementById('jsoneditor');
</script>
<script src="jsoneditor.bundlejs"></script>
<script src="../../core/lib/interface.js"></script>
<script>
var settings = {
menu: [
{ type: 'state', title: 'Check for updates', id: 'update.home_assistant_core_update' },
{ type: 'service', title: 'Create Notification', domain: 'persistent_notification', service: 'create',
data: { 'message': 'test notification', 'title': 'Test'} },
{ type: 'menu', title: 'Sub-menu', data:
[
{ type: 'state', title: 'Check for Supervisor updates', id: 'update.home_assistant_supervisor_update' },
{ type: 'service', title: 'Restart HA', domain: 'homeassistant', service: 'restart', silent: true, data: {} }
]
},
{ type: 'service', title: 'Custom Notification', domain: 'persistent_notification', service: 'create',
data: { 'title': 'Not via input'},
input: { 'message': { options: [], value: 'Pre-filled text' },
'notification_id': { options: [ 123, 456, 136 ], value: 999, label: "ID" } } },
]
};
function onInit() {
// read in existing settings from the Bangle
try {
Util.readStorageJSON('hadash.json', currentSettings => {
if (currentSettings) {
settings = currentSettings;
if ('HAbaseUrl' in settings)
document.getElementById('HAbaseUrl').value = settings.HAbaseUrl;
if ('HAtoken' in settings)
document.getElementById('HAtoken').value = settings.HAtoken;
if ('menu' in settings)
JSONEditorInstance.update({ text: undefined, json: settings.menu });
}
});
} catch (e) {
console.log("Failed to read existing settings: "+e);
}
}
document.getElementById("upload").addEventListener("click", function() {
if (JSONEditorInstance.get().json) {
settings.menu = JSONEditorInstance.get().json;
} else {
settings.menu = JSON.parse(JSONEditorInstance.get().text);
}
if (! settings.menu) {
document.getElementById("status").innerHTML = 'Generating the menu failed (or menu is empty) - upload aborted!';
return;
}
settings.HAbaseUrl = document.getElementById('HAbaseUrl').value;
settings.HAtoken = document.getElementById('HAtoken').value;
Util.writeStorage('hadash.json', JSON.stringify(settings), () => {
document.getElementById("status").innerHTML = 'HA-Dash configuration successfully uploaded to Bangle!';
});
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,22 @@
/*
* JSONEditor wrapper
*
* This script is bundled together with the actual JSONEditor (https://github.com/josdejong/svelte-jsoneditor)
* using ESBuild (see below).
*
* The following global variables need to be defined before including the jsoneditor-bundle.js:
*
* JSONEditorInstance will contain the new JSONEditor instance
* JSONEditor_target element ID of container (<div>) for the JSONEditor
*
* To build the bundle, run the following commands:
* npm install esbuild
* npm install vanilla-jsoneditor
* ./node_modules/.bin/esbuild jsoneditor.wrapperjs --bundle --outfile=jsoneditor.bundlejs
*
*/
import { JSONEditor } from 'vanilla-jsoneditor/standalone.js'
JSONEditorInstance = new JSONEditor({ target: JSONEditor_target, props: {} });

20
apps/hadash/metadata.json Normal file
View File

@ -0,0 +1,20 @@
{
"id": "hadash",
"name": "Home-Assistant Dashboard",
"shortName":"HA-Dash",
"version":"1.01",
"description": "Interact with Home-Assistant (query states, call services)",
"icon": "hadash.png",
"screenshots": [{ "url": "screenshot.png" }],
"type": "app",
"tags": "tool,online",
"supports": ["BANGLEJS2"],
"dependencies": { "textinput": "type" },
"readme": "README.md",
"interface": "interface.html",
"storage": [
{ "name":"hadash.app.js", "url":"hadash.app.js" },
{ "name":"hadash.img", "url":"hadash-icon.js", "evaluate":true }
],
"data": [{ "name":"hadash.json" }]
}

BIN
apps/hadash/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -2,3 +2,4 @@
0.02: Minor code improvements
0.03: Better scrolling behaviour
0.04: Fix incorrect appRect handling (missing back buttons and doubled menu titles)
0.05: Bring in change from the firmware implementation forwarding the type of touch (short/long).

View File

@ -50,7 +50,7 @@
if ((menuScrollMin<0 || i>=0) && i<options.c){
let yAbs = (e.y + rScroll - R.y);
let yInElement = yAbs - i*options.h;
options.select(i, {x:e.x, y:yInElement});
options.select(i, {x:e.x, y:yInElement, type:e.type});
}
};

View File

@ -1,5 +1,5 @@
(function(){E.showScroller=function(c){function k(a){return a*c.h+Bangle.appRect.y-l}function h(a){return Math.floor((a+l-Bangle.appRect.y)/c.h)}if(!c)return Bangle.setUI();let n,f=0,p=0,q=0,r=0,t=0,m=0|c.scrollMin;const u=()=>{let a=c.h*c.c-Bangle.appRect.h;a<m&&(a=m);return a},v=()=>{let a=Bangle.appRect;g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);for(var b=h(a.y),d=Math.min(h(a.y2),c.c-1);b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-
1,g.getHeight()-1)},w=()=>{let a=Bangle.appRect;.1<f&&(n||(n=setTimeout(w,0)),f*=1-(Date.now()-r)/8E3,.1>=f?f=0:e.scroll-=f*q);var b=u();e.scroll>b&&(e.scroll=b,f=0);e.scroll<m&&(e.scroll=m,f=0);b=l;l=e.scroll&-2;if(b-=l){g.reset().setClipRect(a.x,a.y,a.x2,a.y2).scroll(0,b);if(0>b){b=Math.max(a.y2-(1-b),a.y);g.setClipRect(a.x,b,a.x2,a.y2);var d=h(b);for(b=k(d);b<a.y2;b+=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d++}else for(b=Math.min(a.y+b,a.y2),g.setClipRect(a.x,a.y,a.x2,b),d=h(b),k(d),b=k(d);b>a.y-
c.h;b-=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);n=void 0}};let x={mode:"custom",back:c.back,drag:a=>{let b=Date.now();q=Math.sign(a.dy);e.scroll-=a.dy;if(0<a.b){r=b;if(!t||0>p*q&&0<a.dy*q)t=r,p=f=0;p+=a.dy}else 150>b-r&&(f=q*p/(b-t)*100),t=0;w()},touch:(a,b)=>{a=Bangle.appRect;if(!(b.y<a.y-4)){p=f=0;var d=h(b.y);(0>m||0<=d)&&d<c.c&&c.select(d,{x:b.x,y:b.y+l-a.y-d*c.h})}},redraw:v};c.remove&&(x.remove=()=>{n&&clearTimeout(n);c.remove()});Bangle.setUI(x);
let e={scroll:E.clip(0|c.scroll,m,u()),draw:()=>{let a=Bangle.appRect;g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);var b=h(a.y);let d=Math.min(h(a.y2),c.c-1);for(;b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let b=Bangle.appRect,d=k(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==v},l=e.scroll&
-2;e.draw();g.flip();return e}})()
c.h;b-=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);n=void 0}};let x={mode:"custom",back:c.back,drag:a=>{let b=Date.now();q=Math.sign(a.dy);e.scroll-=a.dy;if(0<a.b){r=b;if(!t||0>p*q&&0<a.dy*q)t=r,p=f=0;p+=a.dy}else 150>b-r&&(f=q*p/(b-t)*100),t=0;w()},touch:(a,b)=>{a=Bangle.appRect;if(!(b.y<a.y-4)){p=f=0;var d=h(b.y);(0>m||0<=d)&&d<c.c&&c.select(d,{x:b.x,y:b.y+l-a.y-d*c.h,type:b.type})}},redraw:v};c.remove&&(x.remove=()=>{n&&clearTimeout(n);c.remove()});
Bangle.setUI(x);let e={scroll:E.clip(0|c.scroll,m,u()),draw:()=>{let a=Bangle.appRect;g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);var b=h(a.y);let d=Math.min(h(a.y2),c.c-1);for(;b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let b=Bangle.appRect,d=k(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==
v},l=e.scroll&-2;e.draw();g.flip();return e}})()

View File

@ -1,12 +1,12 @@
{ "id": "kineticscroll",
"name": "Kinetic Scroll",
"shortName":"Kinetic Scroll",
"version": "0.04",
"version": "0.05",
"description": "Replacement for the system scroller with kinetic scrolling.",
"icon": "app.png",
"type": "bootloader",
"tags": "system",
"supports" : ["BANGLEJS2"],
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"kineticscroll.boot.js","url":"boot.min.js"}

View File

@ -4,3 +4,4 @@
0.04: clock_info is loaded before widgets to match other clocks
0.05: fix alignment of clock items caused by 0.04 (fix #2970)
0.06: Minor code improvements
0.07: fix special characters in clockinfo menus

View File

@ -38,7 +38,7 @@ let clockInfoDraw = (itm, info, options) => {
if (info.img) g.drawImage(info.img, options.x+2, options.y+2);
var title = clockInfoItems[options.menuA].name;
var text = info.text.toString().toUpperCase();
var text = info.text.toString().toUpperCase().replace(/[^A-Z0-9]/g, "");
if (title!="Bangle") g.setFontAlign(1,0).drawString(title.toUpperCase(), options.x+options.w-2, options.y+14);
if (g.setFont("7Seg:2").stringWidth(text)+8>options.w) g.setFont("7Seg");
g.setFontAlign(0,0).drawString(text, options.x+options.w/2, options.y+40);
@ -82,4 +82,4 @@ Bangle.drawWidgets();
let clockInfoItems = require("clock_info").load();
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x, y:R.y, w:midX-2, h:barY-R.y-2, draw : clockInfoDraw});
let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+2, y:R.y, w:midX-3, h:barY-R.y-2, draw : clockInfoDraw});
}
}

View File

@ -1,6 +1,6 @@
{ "id": "lcdclock",
"name": "LCD Clock",
"version": "0.06",
"version": "0.07",
"description": "A Casio-style clock, with ClockInfo areas at the top and bottom. Tap them and swipe up/down to toggle between different information",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Minor code improvements
0.03: fix special characters in clockinfo menus

View File

@ -51,7 +51,7 @@ let clockInfoDraw = (itm, info, options) => {
if (info.img) {
g.drawImage(info.img, options.x+1,options.y+2);
}
var text = info.text.toString().toUpperCase();
var text = info.text.toString().toUpperCase().replace(/[^A-Z0-9]/g, "");
if (g.setFont("7Seg:2").stringWidth(text)+24-2>options.w) g.setFont("7Seg");
g.setFontAlign(0,-1).drawString(text, options.x+options.w/2+13, options.y+6);
};

View File

@ -1,6 +1,6 @@
{ "id": "lcdclockplus",
"name": "LCD Clock Plus",
"version": "0.02",
"version": "0.03",
"description": "A Casio-style clock, with four ClockInfo areas at the top and bottom. Tap them and swipe up/down and left/right to toggle between different information.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],

View File

@ -91,13 +91,6 @@ module.exports = {
"no-unused-vars"
]
},
"apps/mtnclock/app.js": {
"hash": "c48e3ed1a605e6a131e5947718e26cc9481c6eeab36c5670bb74f0c58cb96cd8",
"rules": [
"no-unused-vars",
"no-undef"
]
},
"apps/mmonday/manic-monday.js": {
"hash": "2eff38d4d1cde2b9f17800a554da58300a6250de3e014995a6958a11bcb5b76a",
"rules": [
@ -189,13 +182,6 @@ module.exports = {
"no-unused-vars"
]
},
"apps/skyspy/skyspy.app.js": {
"hash": "49a727a4c052e8c6322a502750ca036b0d58896f476b1cffebe9c53e426c8bcc",
"rules": [
"no-unused-vars",
"no-undef"
]
},
"apps/scribble/app.js": {
"hash": "6d13abd27bab8009a6bdabe1df2df394bc14aac20c68f67e8f8b085fa6b427cd",
"rules": [
@ -463,12 +449,6 @@ module.exports = {
"no-undef"
]
},
"apps/calculator/app.js": {
"hash": "fcb7c7b6c4ec5ce0f425d2a690baab8da235a12e685fe2680cbd4cf2cfdef0b0",
"rules": [
"no-unused-vars"
]
},
"apps/bowserWF/app.js": {
"hash": "83feae92eda4c25028892b5b8b7d1b04f7ec3bb45f51eeba517a80b3ab2053cf",
"rules": [
@ -673,12 +653,6 @@ module.exports = {
"no-undef"
]
},
"apps/slopeclockpp/app.js": {
"hash": "f6e3f6723ed4fc71a3cacb3d24ec4fb47447a65d495adccb9d86333c19d4d0bd",
"rules": [
"no-undef"
]
},
"apps/slopeclock/app.js": {
"hash": "fe29b4674b3e3a791898fd2067acc7c0fcb433dc8d4a8e8e3338c6d42a6c468f",
"rules": [
@ -1195,12 +1169,6 @@ module.exports = {
"no-undef"
]
},
"apps/aviatorclk/aviatorclk.app.js": {
"hash": "5bc629c86eada72533fc664e01a47e05f207baeeae2092323ae4d04bd1c9fe9a",
"rules": [
"no-undef"
]
},
"apps/authentiwatch/app.js": {
"hash": "02aafe5b6f3f3dce979d5b04f4660f90261e0c893cb39e9eeb2cf0bdb2c256b8",
"rules": [
@ -1304,7 +1272,7 @@ module.exports = {
]
},
"apps/kineticscroll/boot.min.js": {
"hash": "1345b3c556f1a268a81c3a57825d096d9bbce9740217339aa6d79223d9daad4d",
"hash": "0814125a19aff5c608b9357926cd4a4c038334e31b0d07a70deefaf2c77959b9",
"rules": [
"no-cond-assign"
]

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